diff --git a/.meepctl-repocfg.yaml b/.meepctl-repocfg.yaml index 1bc16969312513bfa7ad8e57157e1cb814ede077..468a1c04d92ea253989d826bd5b6bcae02f1102f 100644 --- a/.meepctl-repocfg.yaml +++ b/.meepctl-repocfg.yaml @@ -48,7 +48,7 @@ repo: # https config https-port: 443 # certificate authority (none|self-signed|lets-encrypt) default: none - ca: lets-encrypt + ca: self-signed # lets-encrypt production server (true) or staging server (false) le-server-prod: true @@ -104,7 +104,7 @@ repo: # enable influx data backups enabled: true # object store url - url: https://metrics.mec-platform.etsi.org/ + url: https://metrics.try-mec.etsi.org/ # object store configuration secret secret: meep-influx-objstore-config # Number of days to retain daily data backups @@ -152,11 +152,11 @@ repo: # 1h downsampled data retention resolution-1h: 10y # Thanos long-term storage archive - thanos-archive: + #thanos-archive: # enable Thanos archive - enabled: true + # enabled: true # archive object store configuration secret - secret: meep-thanos-archive-objstore-config + #secret: meep-thanos-archive-objstore-config # Garbage Collection configuration gc: @@ -308,6 +308,8 @@ repo: # - meep-tc-engine # - meep-app-enablement # - meep-vis + # - meep-iot + # - meep-sss # - meep-federation # location of API specifications api: @@ -423,9 +425,15 @@ repo: meep-app-enablement: charts/meep-app-enablement meep-virt-chart-templates: charts/meep-virt-chart-templates meep-vis: charts/meep-vis + meep-iot: charts/meep-iot + meep-sss: charts/meep-sss meep-federation: charts/meep-federation meep-cloud-mosquitto: charts/meep-cloud-mosquitto meep-mosquitto: charts/meep-mosquitto + meep-acme-mn-cse: charts/meep-acme-mn-cse + meep-acme-in-cse: charts/meep-acme-in-cse + meep-tinyiot-mn-cse: charts/meep-tinyiot-mn-cse + meep-tinyiot-in-cse: charts/meep-tinyiot-in-cse # list of sandbox specific pods sandbox-pods: - meep-gis-engine @@ -441,6 +449,8 @@ repo: # - meep-tc-engine # - meep-app-enablement # - meep-vis + # - meep-iot + # - meep-sss # - meep-federation meep-webhook: # location of source code @@ -944,6 +954,74 @@ repo: 'entrypoint.sh': go-apps/meep-vis/entrypoint.sh # location of grid map file 'grid_map.yaml': go-packages/meep-vis-traffic-mgr/grid_map.yaml + meep-iot: + # location of source code + src: go-apps/meep-iot + # location of binary + bin: bin/meep-iot + # location of deployment chart + chart: charts/meep-iot + # user supplied value file located @ .meep/user/values (use below file name) + chart-user-values: meep-iot.yaml + # extra build flags + build-flags: + - -mod=vendor + # enable meepctl build + build: true + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: true + # supports code coverage measurement when built in codecov mode + codecov: true + # supports linting + lint: true + # location of API specifications + api: + - name: 'AdvantEDGE IOT APIs REST API' + file: go-apps/meep-iot/api/swagger.yaml + # location of user supplied API specifications + user-api: + - name: 'IOT APIs REST API' + file: config/api/iot-api.yaml + # resources available to docker container image + docker-data: + # location of entry script + 'entrypoint.sh': go-apps/meep-iot/entrypoint.sh + meep-sss: + # location of source code + src: go-apps/meep-sss + # location of binary + bin: bin/meep-sss + # location of deployment chart + chart: charts/meep-sss + # user supplied value file located @ .meep/user/values (use below file name) + chart-user-values: meep-sss.yaml + # extra build flags + build-flags: + - -mod=vendor + # enable meepctl build + build: true + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: true + # supports code coverage measurement when built in codecov mode + codecov: true + # supports linting + lint: true + # location of API specifications + api: + - name: 'AdvantEDGE SENSORS SHARING APIs REST API' + file: go-apps/meep-sss/api/swagger.yaml + # location of user supplied API specifications + user-api: + - name: 'SENSORS SHARING APIs REST API' + file: config/api/sss-api.yaml + # resources available to docker container image + docker-data: + # location of entry script + 'entrypoint.sh': go-apps/meep-sss/entrypoint.sh meep-federation: # location of source code src: go-apps/meep-federation @@ -978,7 +1056,97 @@ repo: docker-data: # location of entry script 'entrypoint.sh': go-apps/meep-federation/entrypoint.sh - + meep-mosquitto: + # location of source code + src: go-apps/meep-mosquitto + bin: bin/meep-mosquitto + # enable meepctl build + build: false + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: false + # supports code coverage measurement when built in codecov mode + codecov: false + # supports linting + lint: false + docker-data: + 'mosquitto.conf': go-apps/meep-mosquitto/mosquitto.conf + 'listener.conf': go-apps/meep-mosquitto/listener.conf + 'entrypoint.sh': go-apps/meep-mosquitto/entrypoint.sh + meep-tinyiot-in-cse: + # location of source code + src: go-apps/meep-iot-pltf/tinyiot-in-cse + bin: bin/meep-iot-pltf/meep-tinyiot-in-cse + # enable meepctl build + build: false + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: false + # supports code coverage measurement when built in codecov mode + codecov: false + # supports linting + lint: false + # TinyIoT resources available to docker container image + docker-data: + 'entrypoint.sh': go-apps/meep-iot-pltf/tinyiot-in-cse/entrypoint.sh + 'source': go-apps/meep-iot-pltf/tinyiot-in-cse/tinyIoT/source + meep-tinyiot-mn-cse: + # location of source code + src: go-apps/meep-iot-pltf/tinyiot-mn-cse + bin: bin/meep-iot-pltf/meep-tinyiot-mn-cse + # enable meepctl build + build: false + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: false + # supports code coverage measurement when built in codecov mode + codecov: false + # supports linting + lint: false + # TinyIoT resources available to docker container image + docker-data: + 'entrypoint.sh': go-apps/meep-iot-pltf/tinyiot-mn-cse/entrypoint.sh + 'source': go-apps/meep-iot-pltf/tinyiot-mn-cse/tinyIoT/source + meep-in-cse: + # location of source code + src: go-apps/meep-iot-pltf/in-cse + bin: bin/meep-iot-pltf/in-cse + # enable meepctl build + build: false + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: false + # supports code coverage measurement when built in codecov mode + codecov: false + # supports linting + lint: false + docker-data: + # location of entry script + 'entrypoint.sh': go-apps/meep-iot-pltf/in-cse/entrypoint.sh + 'acme.ini': go-apps/meep-iot-pltf/in-cse/acme.ini + meep-om2m-acme: + # location of source code + src: go-apps/meep-iot-pltf/acme + bin: bin/meep-iot-pltf/acme + # enable meepctl build + build: false + # enable meepctl dockerize + dockerize: true + # enable meepctl deploy/delete + deploy: false + # supports code coverage measurement when built in codecov mode + codecov: false + # supports linting + lint: false + docker-data: + # location of entry script + 'entrypoint.sh': go-apps/meep-iot-pltf/acme/entrypoint.sh + 'acme.ini': go-apps/meep-iot-pltf/acme/acme.ini + 'acme': go-apps/meep-iot-pltf/acme/acme #------------------------------ # Dependencies #------------------------------ @@ -1339,6 +1507,36 @@ repo: src: go-packages/meep-wais-client # supports linting lint: false + meep-vis-client: + # location of source code + src: go-packages/meep-dai-client + # supports linting + lint: false + meep-iot-client: + # location of source code + src: go-packages/meep-iot-client + # supports linting + lint: false + meep-sss-client: + # location of source code + src: go-packages/meep-sss-client + # supports linting + lint: false + meep-federation-mgr: + # location of source code + src: go-packages/meep-federation-mgr + # supports linting + lint: false + meep-vis-traffic-mgr: + # location of source code + src: go-packages/meep-dai-mgr + # supports linting + lint: false + meep-iot-mgr: + # location of source code + src: go-packages/meep-iot-mgr + # supports linting + lint: false meep-vis-client: # location of source code src: go-packages/meep-vis-client diff --git a/AUTHORS b/AUTHORS index e8b95b3a62546e21c320078f0a10c46a9617f869..5595e0969f865b8c7ef38dae1750265af213e5b8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,3 +17,8 @@ FSCOM InterDigital Communications Inc xFlow Research (Pvt.) Inc. FSCOM + +[v1.11.0 xFlow/FSCOM] +InterDigital Communications Inc +xFlow Research (Pvt.) Inc. +FSCOM diff --git a/README.md b/README.md index 2f18c63af973559faa98387b73c023b557ade723..bdf334ef9ee3d983cd6d8485f79f4e25e31ae9f7 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,19 @@ ------ +**_What's New in v1.11.0 xFlow/FSCOM!_** + +:zap: **New edge native service: [ETSI MEC040 - MEC Federation API](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#mec-federation-service)** + +:zap: ** [ETSI MEC033](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#iot-api) and [ETSI MEC046](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#mec-sensors-sharing) :arrow_up:** + **_What's New in v1.10.0!_** :zap: **New edge native service: [ETSI MEC Profile for CAPIF](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#edge-platform-application-enablement-service)** :zap: **New edge native service: [ETSI MEC040 - MEC Federation API](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#mec-federation-service)** -:zap: **Service API upgrade to MEC Phase 3 for [ETSI MEC011](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#edge-platform-application-enablement-service), [ETSI MEC012](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#radio-network-information-service), [ETSI MEC013](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#location-service), [ETSI MEC021](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#application-mobility-service), [ETSI MEC030](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#v2x-information-service), [ETSI MEC033](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#iot-api), [ETSI MEC040](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#mec-federation-service) and [ETSI MEC046](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#mec-sensors-sharing) :arrow_up:** +:zap: **Service API upgrade to MEC Phase 3 for [ETSI MEC011](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#edge-platform-application-enablement-service), [ETSI MEC012](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#radio-network-information-service), [ETSI MEC013](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#location-service), [ETSI MEC021](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#application-mobility-service), [ETSI MEC030](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#v2x-information-service) and [ETSI MEC040](https://interdigitalinc.github.io/AdvantEDGE/docs/overview/edge-services/#mec-federation-service) :arrow_up:** :zap: **New command line api to develop MEC application/service without GUI** diff --git a/charts/meep-acme-in-cse/Chart.yaml b/charts/meep-acme-in-cse/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ac6ed803ecd8d82104eb329a7794769d96e340d4 --- /dev/null +++ b/charts/meep-acme-in-cse/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: MEEP OM2M ACME IN-CSE Service Helm chart for Kubernetes +name: meep-acme-in-cse +version: 1.0.0 diff --git a/charts/meep-acme-in-cse/templates/_helpers.tpl b/charts/meep-acme-in-cse/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..6abf83e8767b85ba7adc455907240616adea0bff --- /dev/null +++ b/charts/meep-acme-in-cse/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "meep-acme-in-cse.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "meep-acme-in-cse.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "meep-acme-in-cse.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/meep-acme-in-cse/templates/clusterrolebinding.yaml b/charts/meep-acme-in-cse/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bfca9cc285b661e34192c1bcd1fc41a12a15bcd6 --- /dev/null +++ b/charts/meep-acme-in-cse/templates/clusterrolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{ .Release.Namespace }}:{{ template "meep-acme-in-cse.fullname" . }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: {{ template "meep-acme-in-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + \ No newline at end of file diff --git a/charts/meep-acme-in-cse/templates/codecov-pv.yaml b/charts/meep-acme-in-cse/templates/codecov-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..33264a7def079bca2222ad2581e072a1486983c5 --- /dev/null +++ b/charts/meep-acme-in-cse/templates/codecov-pv.yaml @@ -0,0 +1,35 @@ +{{- if .Values.codecov.enabled}} +kind: PersistentVolume +apiVersion: v1 +metadata: + name: meep-acme-in-cse-codecov-pv +spec: + storageClassName: meep-acme-in-cse-codecov-sc + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: {{ .Values.codecov.location }} + +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: meep-acme-in-cse-codecov-sc +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: meep-acme-in-cse-codecov-pvc +spec: + storageClassName: meep-acme-in-cse-codecov-sc + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +{{- end}} diff --git a/charts/meep-acme-in-cse/templates/deployment.yaml b/charts/meep-acme-in-cse/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6dd97e1bd8f5de8136f3ab9f5bc12af037b5ff7e --- /dev/null +++ b/charts/meep-acme-in-cse/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "meep-acme-in-cse.fullname" . }} + labels: + app: {{ template "meep-acme-in-cse.name" . }} + chart: {{ template "meep-acme-in-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + app: {{ template "meep-acme-in-cse.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "meep-acme-in-cse.name" . }} + release: {{ .Release.Name }} + meepOrigin: {{ .Values.meepOrigin }} + spec: + serviceAccountName: {{ template "meep-acme-in-cse.fullname" . }} + {{- if .Values.codecov.enabled}} + volumes: + - name: codecov-storage + persistentVolumeClaim: + claimName: meep-acme-in-cse-codecov-pvc + {{- end}} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.deployment.port }} + protocol: {{ .Values.deployment.protocol }} + env: + {{- range $key, $value := .Values.image.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add Kubernetes metadata for NodePort retrieval + - name: MEEP_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MEEP_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.codecov.enabled}} + volumeMounts: + - name: codecov-storage + mountPath: /codecov + {{- end}} + terminationGracePeriodSeconds: 5 + initContainers: + {{- range $value := .Values.deployment.dependencies.system }} + - name: init-system-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }}.kube-system ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} + {{- range $value := .Values.deployment.dependencies.namespace }} + - name: init-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }} ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} \ No newline at end of file diff --git a/charts/meep-acme-in-cse/templates/ingress.yaml b/charts/meep-acme-in-cse/templates/ingress.yaml new file mode 100755 index 0000000000000000000000000000000000000000..da4bc2e04a3c8cee046d7a21b08d15bd637e916f --- /dev/null +++ b/charts/meep-acme-in-cse/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := .Values.service.name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + labels: + app: {{ template "meep-acme-in-cse.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - http: + paths: + {{- range $path := .paths }} + - path: {{ $path }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} + {{- if .name }} + host: {{ .name }} + {{- end }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/meep-acme-in-cse/templates/monitor.yaml b/charts/meep-acme-in-cse/templates/monitor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8d7b6075bb39118eedc5c3bb0fbee87b33ee13b0 --- /dev/null +++ b/charts/meep-acme-in-cse/templates/monitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "meep-acme-in-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "meep-acme-in-cse.name" . }} + chart: {{ template "meep-acme-in-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} + {{- if .Values.prometheus.monitor.additionalLabels }} +{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "meep-acme-in-cse.name" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} +{{- if .Values.prometheus.monitor.relabelings }} + relabelings: +{{ toYaml .Values.prometheus.monitor.relabelings | indent 6 }} +{{- end }} +{{- end }} diff --git a/charts/meep-acme-in-cse/templates/service.yaml b/charts/meep-acme-in-cse/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d693f3eaefb3af7d17de1512d93d5a91073a32ef --- /dev/null +++ b/charts/meep-acme-in-cse/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name }} + labels: + app: {{ template "meep-acme-in-cse.name" . }} + chart: {{ template "meep-acme-in-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "meep-acme-in-cse.name" . }} + release: {{ .Release.Name }} + ports: + - name: meep-acme-in-cse + port: {{ .Values.service.port }} + targetPort: {{ .Values.deployment.port }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.monitor.enabled}} + - name: metrics + port: {{ .Values.prometheus.monitor.port }} + targetPort: {{ .Values.prometheus.monitor.port }} + protocol: TCP + {{- end}} + diff --git a/charts/meep-acme-in-cse/templates/serviceaccount.yaml b/charts/meep-acme-in-cse/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d162208863df0659918cb849f54d789e9c6864dd --- /dev/null +++ b/charts/meep-acme-in-cse/templates/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "meep-acme-in-cse.fullname" . }} diff --git a/charts/meep-acme-in-cse/values-template.yaml b/charts/meep-acme-in-cse/values-template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d338fe3d9e530dd0c2a2ec7b62cf9caa6ffbd99b --- /dev/null +++ b/charts/meep-acme-in-cse/values-template.yaml @@ -0,0 +1,99 @@ +# Default values for meep-acme-meep-acme-in-cse +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +deployment: + replicas: 1 + port: 80 + protocol: TCP + dependencies: + system: + {{- if not .IsMepService }} + - kube-dns + {{- end }} + namespace: + +image: + repository: meep-docker-registry:30001/meep-acme-in-cse + tag: latest + pullPolicy: Always + # entrypointScript: /entrypoint.sh + env: + MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + NR_MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + MEEP_INSTANCE_ID: {{.InstanceId}} + MEEP_SANDBOX_NAME: {{.SandboxName}} + MEEP_SVC_PATH: /in-cse + MEEP_HOST_URL: {{.HostUrl}} + SERVER_TYPE: IN + SERVER_PORT: 80 + CSE_BASE_NAME: laboai-cse-in + CSE_BASE_RI: laboai-id-in + MQTT_HOST: meep-mosquitto + MQTT_PORT: 1883 + MQTT_USERNAME: test + MQTT_PASSWORD: mqttq + MEEP_TOPIC: {{.SandboxName}}/acme-in-cse + ENABLE_COAP: false + ENABLE_WSP: false + {{- if .IsMepService }} + MEEP_MEP_NAME: {{.MepName}} + {{- end }} + {{- if eq .AppEnablement "local" }} + MEEP_APP_ENABLEMENT: {{.MepName}}-meep-app-enablement + {{- else if eq .AppEnablement "global" }} + MEEP_APP_ENABLEMENT: meep-app-enablement + {{- end }} + {{- range .Env}} + {{.}} + {{- end}} + +service: + {{- if .IsMepService }} + name: {{.MepName}}-meep-acme-in-cse + {{- else }} + name: meep-acme-in-cse + {{- end }} + type: ClusterIP + port: 80 + +ingress: + enabled: true + hosts: + - name: '' + paths: + {{- if .IsMepService }} + - /{{.SandboxName}}/{{.MepName}}/meep-acme-in-cse + {{- else }} + - /{{.SandboxName}}/meep-acme-in-cse + {{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: {{ .HttpsOnly }} + {{- if .IsMepService }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/{{.MepName}}/meep-acme-in-cse(/|$)(.*)$ /$2 break; + {{- else }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/meep-acme-in-cse(/|$)(.*)$ /$2 break; + {{- end }} + {{- if .AuthEnabled }} + nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-acme-in-cse&sbox={{.SandboxName}}&mep={{.MepName}} + {{- end }} + labels: {} + tls: + +prometheus: + monitor: + enabled: true + port: 9000 + interval: 10s + additionalLabels: {} + relabelings: [] + scrapeTimeout: 5s + +codecov: + enabled: false + location: "</codecov/meep-acme-in-cse" + +meepOrigin: core diff --git a/charts/meep-acme-mn-cse/Chart.yaml b/charts/meep-acme-mn-cse/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8c1b19948ce3e28ba297e8121fb2914980e5043c --- /dev/null +++ b/charts/meep-acme-mn-cse/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: MEEP OM2M ACME MN-CSE Service Helm chart for Kubernetes +name: meep-acme-mn-cse +version: 1.0.0 diff --git a/charts/meep-acme-mn-cse/templates/_helpers.tpl b/charts/meep-acme-mn-cse/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..f646323ae9f068c0c6506b646d44b7aaeb816473 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "meep-acme-mn-cse.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "meep-acme-mn-cse.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "meep-acme-mn-cse.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/meep-acme-mn-cse/templates/clusterrolebinding.yaml b/charts/meep-acme-mn-cse/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..41a7bbfdb9bdc329243819b0c809b333c8193d2d --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/clusterrolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{ .Release.Namespace }}:{{ template "meep-acme-mn-cse.fullname" . }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: {{ template "meep-acme-mn-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + \ No newline at end of file diff --git a/charts/meep-acme-mn-cse/templates/codecov-pv.yaml b/charts/meep-acme-mn-cse/templates/codecov-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..007aac8c6fc6939891d3e23e5392e9e33268bea2 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/codecov-pv.yaml @@ -0,0 +1,35 @@ +{{- if .Values.codecov.enabled}} +kind: PersistentVolume +apiVersion: v1 +metadata: + name: meep-acme-mn-cse-codecov-pv +spec: + storageClassName: meep-acme-mn-cse-codecov-sc + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: {{ .Values.codecov.location }} + +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: meep-acme-mn-cse-codecov-sc +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: meep-acme-mn-cse-codecov-pvc +spec: + storageClassName: meep-acme-mn-cse-codecov-sc + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +{{- end}} diff --git a/charts/meep-acme-mn-cse/templates/deployment.yaml b/charts/meep-acme-mn-cse/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fb9752f282c6fded391209e45c3fd0fe09e03ed6 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "meep-acme-mn-cse.fullname" . }} + labels: + app: {{ template "meep-acme-mn-cse.name" . }} + chart: {{ template "meep-acme-mn-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + app: {{ template "meep-acme-mn-cse.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "meep-acme-mn-cse.name" . }} + release: {{ .Release.Name }} + meepOrigin: {{ .Values.meepOrigin }} + spec: + serviceAccountName: {{ template "meep-acme-mn-cse.fullname" . }} + {{- if .Values.codecov.enabled}} + volumes: + - name: codecov-storage + persistentVolumeClaim: + claimName: meep-acme-mn-cse-codecov-pvc + {{- end}} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.deployment.port }} + protocol: {{ .Values.deployment.protocol }} + env: + {{- range $key, $value := .Values.image.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add Kubernetes metadata for NodePort retrieval + - name: MEEP_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MEEP_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.codecov.enabled}} + volumeMounts: + - name: codecov-storage + mountPath: /codecov + {{- end}} + terminationGracePeriodSeconds: 5 + initContainers: + {{- range $value := .Values.deployment.dependencies.system }} + - name: init-system-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }}.kube-system ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} + {{- range $value := .Values.deployment.dependencies.namespace }} + - name: init-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }} ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} \ No newline at end of file diff --git a/charts/meep-acme-mn-cse/templates/ingress.yaml b/charts/meep-acme-mn-cse/templates/ingress.yaml new file mode 100755 index 0000000000000000000000000000000000000000..d24e51d4ce976404ae882aa9d37c2ea01c36f264 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := .Values.service.name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + labels: + app: {{ template "meep-acme-mn-cse.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - http: + paths: + {{- range $path := .paths }} + - path: {{ $path }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} + {{- if .name }} + host: {{ .name }} + {{- end }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/meep-acme-mn-cse/templates/monitor.yaml b/charts/meep-acme-mn-cse/templates/monitor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e03e63890ade9b454eccc17b85025bd26f9e4df5 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/monitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "meep-acme-mn-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "meep-acme-mn-cse.name" . }} + chart: {{ template "meep-acme-mn-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} + {{- if .Values.prometheus.monitor.additionalLabels }} +{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "meep-acme-mn-cse.name" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} +{{- if .Values.prometheus.monitor.relabelings }} + relabelings: +{{ toYaml .Values.prometheus.monitor.relabelings | indent 6 }} +{{- end }} +{{- end }} diff --git a/charts/meep-acme-mn-cse/templates/service.yaml b/charts/meep-acme-mn-cse/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e8931607f55063c681fa769addbe396c3a212d45 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name }} + labels: + app: {{ template "meep-acme-mn-cse.name" . }} + chart: {{ template "meep-acme-mn-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "meep-acme-mn-cse.name" . }} + release: {{ .Release.Name }} + ports: + - name: meep-acme-mn-cse + port: {{ .Values.service.port }} + targetPort: {{ .Values.deployment.port }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.monitor.enabled}} + - name: metrics + port: {{ .Values.prometheus.monitor.port }} + targetPort: {{ .Values.prometheus.monitor.port }} + protocol: TCP + {{- end}} + diff --git a/charts/meep-acme-mn-cse/templates/serviceaccount.yaml b/charts/meep-acme-mn-cse/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ad321278530146a268a2060a94570f11058f616 --- /dev/null +++ b/charts/meep-acme-mn-cse/templates/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "meep-acme-mn-cse.fullname" . }} diff --git a/charts/meep-acme-mn-cse/values-template.yaml b/charts/meep-acme-mn-cse/values-template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..54593b6f226e95d7cc2bbc3c2e94a95c4c767a5d --- /dev/null +++ b/charts/meep-acme-mn-cse/values-template.yaml @@ -0,0 +1,103 @@ +# Default values for meep-acme-mn-cse +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +deployment: + replicas: 1 + port: 80 + protocol: TCP + dependencies: + system: + {{- if not .IsMepService }} + - kube-dns + {{- end }} + namespace: + +image: + repository: meep-docker-registry:30001/meep-acme-mn-cse + tag: latest + pullPolicy: Always + # entrypointScript: /entrypoint.sh + env: + MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + NR_MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + MEEP_INSTANCE_ID: {{.InstanceId}} + MEEP_SANDBOX_NAME: {{.SandboxName}} + MEEP_SVC_PATH: /meep-acme-mn-cse + MEEP_HOST_URL: {{.HostUrl}} + SERVER_PORT: 80 + CSE_BASE_NAME: mep-cse-mn + CSE_BASE_RI: acme-mep-id-mn-cse + SERVER_TYPE: MN + REMOTE_CSE_HOST: meep-acme-in-cse + REMOTE_CSE_PORT: 3003 + REMOTE_CSE_ID: laboai-id-in + REMOTE_CSE_NAME: laboai-cse-in + MQTT_HOST: meep-mosquitto + MQTT_PORT: 1883 + MQTT_USERNAME: test + MQTT_PASSWORD: mqttq + MEEP_TOPIC: {{.SandboxName}}/acme-mn-cse + ENABLE_COAP: false + ENABLE_WS: false + {{- if .IsMepService }} + MEEP_MEP_NAME: {{.MepName}} + {{- end }} + {{- if eq .AppEnablement "local" }} + MEEP_APP_ENABLEMENT: {{.MepName}}-meep-app-enablement + {{- else if eq .AppEnablement "global" }} + MEEP_APP_ENABLEMENT: meep-app-enablement + {{- end }} + {{- range .Env}} + {{.}} + {{- end}} + +service: + {{- if .IsMepService }} + name: {{.MepName}}-meep-acme-mn-cse + {{- else }} + name: meep-acme-mn-cse + {{- end }} + type: ClusterIP + port: 80 + +ingress: + enabled: true + hosts: + - name: '' + paths: + {{- if .IsMepService }} + - /{{.SandboxName}}/{{.MepName}}/meep-acme-mn-cse + {{- else }} + - /{{.SandboxName}}/meep-acme-mn-cse + {{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: {{ .HttpsOnly }} + {{- if .IsMepService }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/{{.MepName}}/meep-acme-mn-cse(/|$)(.*)$ /$2 break; + {{- else }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/meep-acme-mn-cse(/|$)(.*)$ /$2 break; + {{- end }} + {{- if .AuthEnabled }} + nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-acme-mn-cse&sbox={{.SandboxName}}&mep={{.MepName}} + {{- end }} + labels: {} + tls: + +prometheus: + monitor: + enabled: true + port: 9000 + interval: 10s + additionalLabels: {} + relabelings: [] + scrapeTimeout: 5s + +codecov: + enabled: false + location: "</codecov/meep-acme-mn-cse" + +meepOrigin: core diff --git a/charts/meep-cloud-mosquitto/.helmignore b/charts/meep-cloud-mosquitto/.helmignore new file mode 100644 index 0000000000000000000000000000000000000000..f0c13194444163d1cba5c67d9e79231a62bc8f44 --- /dev/null +++ b/charts/meep-cloud-mosquitto/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/meep-cloud-mosquitto/Chart.yaml b/charts/meep-cloud-mosquitto/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3acc581bf2ecf29b47f83793b16f05c1b8a5b885 --- /dev/null +++ b/charts/meep-cloud-mosquitto/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: MEEP CLOUD MOSQUITTO Service Helm chart for Kubernetes +name: meep-cloud-mosquitto +version: 1.0.0 diff --git a/charts/meep-cloud-mosquitto/templates/_helpers.tpl b/charts/meep-cloud-mosquitto/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..22e9a04c738cc2493183f6f1cf885ef2e72a2e8a --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "meep-cloud-mosquitto.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "meep-cloud-mosquitto.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "meep-cloud-mosquitto.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/meep-cloud-mosquitto/templates/clusterrolebinding.yaml b/charts/meep-cloud-mosquitto/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ff2430a0fb7f8fd6389b6141e8d6b16e730043e --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/clusterrolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{ .Release.Namespace }}:{{ template "meep-cloud-mosquitto.fullname" . }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: {{ template "meep-cloud-mosquitto.fullname" . }} + namespace: {{ .Release.Namespace }} + \ No newline at end of file diff --git a/charts/meep-cloud-mosquitto/templates/codecov-pv.yaml b/charts/meep-cloud-mosquitto/templates/codecov-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4e1d64f9fa9c5de699c5f166e47e925cbbb71716 --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/codecov-pv.yaml @@ -0,0 +1,35 @@ +{{- if .Values.codecov.enabled}} +kind: PersistentVolume +apiVersion: v1 +metadata: + name: meep-cloud-mosquitto-codecov-pv +spec: + storageClassName: meep-cloud-mosquitto-codecov-sc + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: {{ .Values.codecov.location }} + +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: meep-cloud-mosquitto-codecov-sc +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: meep-cloud-mosquitto-codecov-pvc +spec: + storageClassName: meep-cloud-mosquitto-codecov-sc + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +{{- end}} diff --git a/charts/meep-cloud-mosquitto/templates/deployment.yaml b/charts/meep-cloud-mosquitto/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e2ea109e2ac6ce3f89fa2a9e394240cf8a438407 --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "meep-cloud-mosquitto.fullname" . }} + labels: + app: {{ template "meep-cloud-mosquitto.name" . }} + chart: {{ template "meep-cloud-mosquitto.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + app: {{ template "meep-cloud-mosquitto.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "meep-cloud-mosquitto.name" . }} + release: {{ .Release.Name }} + meepOrigin: {{ .Values.meepOrigin }} + spec: + serviceAccountName: {{ template "meep-cloud-mosquitto.fullname" . }} + {{- if .Values.codecov.enabled}} + volumes: + - name: codecov-storage + persistentVolumeClaim: + claimName: meep-cloud-mosquitto-codecov-pvc + {{- end}} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.deployment.port }} + protocol: {{ .Values.deployment.protocol }} + env: + {{- range $key, $value := .Values.image.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add Kubernetes metadata for NodePort retrieval + - name: MEEP_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MEEP_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.codecov.enabled}} + volumeMounts: + - name: codecov-storage + mountPath: /codecov + {{- end}} + terminationGracePeriodSeconds: 5 + initContainers: + {{- range $value := .Values.deployment.dependencies.system }} + - name: init-system-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }}.kube-system ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} + {{- range $value := .Values.deployment.dependencies.namespace }} + - name: init-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }} ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} \ No newline at end of file diff --git a/charts/meep-cloud-mosquitto/templates/ingress.yaml b/charts/meep-cloud-mosquitto/templates/ingress.yaml new file mode 100755 index 0000000000000000000000000000000000000000..b5fe711d2ff5a6c2acfdd24d61b623a5d9623993 --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := .Values.service.name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + labels: + app: {{ template "meep-cloud-mosquitto.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - http: + paths: + {{- range $path := .paths }} + - path: {{ $path }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} + {{- if .name }} + host: {{ .name }} + {{- end }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/meep-cloud-mosquitto/templates/monitor.yaml b/charts/meep-cloud-mosquitto/templates/monitor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2eb419501bbb5b5ffba1086b11c4ce3e03152d96 --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/monitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "meep-cloud-mosquitto.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "meep-cloud-mosquitto.name" . }} + chart: {{ template "meep-cloud-mosquitto.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} + {{- if .Values.prometheus.monitor.additionalLabels }} +{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "meep-cloud-mosquitto.name" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} +{{- if .Values.prometheus.monitor.relabelings }} + relabelings: +{{ toYaml .Values.prometheus.monitor.relabelings | indent 6 }} +{{- end }} +{{- end }} diff --git a/charts/meep-cloud-mosquitto/templates/service.yaml b/charts/meep-cloud-mosquitto/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..333b6ad99a750b55b2a91ed4e780fe5025959901 --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name }} + labels: + app: {{ template "meep-cloud-mosquitto.name" . }} + chart: {{ template "meep-cloud-mosquitto.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "meep-cloud-mosquitto.name" . }} + release: {{ .Release.Name }} + ports: + - name: meep-cloud-mosquitto + port: {{ .Values.service.port }} + targetPort: {{ .Values.deployment.port }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.monitor.enabled}} + - name: metrics + port: {{ .Values.prometheus.monitor.port }} + targetPort: {{ .Values.prometheus.monitor.port }} + protocol: TCP + {{- end}} + diff --git a/charts/meep-cloud-mosquitto/templates/serviceaccount.yaml b/charts/meep-cloud-mosquitto/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6b2901270da88dd80697799f031116cb5ac0cb1d --- /dev/null +++ b/charts/meep-cloud-mosquitto/templates/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "meep-cloud-mosquitto.fullname" . }} diff --git a/charts/meep-cloud-mosquitto/values-template.yaml b/charts/meep-cloud-mosquitto/values-template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7b0a6a37340f3e209900d6a1871504053ad194a1 --- /dev/null +++ b/charts/meep-cloud-mosquitto/values-template.yaml @@ -0,0 +1,86 @@ +# Default values for meep-cloud-mosquitto +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +deployment: + replicas: 1 + port: 1883 + protocol: TCP + dependencies: + system: + {{- if not .IsMepService }} + - kube-dns + {{- end }} + namespace: + +image: + repository: meep-docker-registry:30001/meep-cloud-mosquitto + tag: latest + pullPolicy: Always + # entrypointScript: /entrypoint.sh + env: + MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + NR_MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + MEEP_INSTANCE_ID: {{.InstanceId}} + MEEP_SANDBOX_NAME: {{.SandboxName}} + MEEP_HOST_URL: {{.HostUrl}} + {{- if .IsMepService }} + MEEP_MEP_NAME: {{.MepName}} + {{- end }} + {{- if eq .AppEnablement "local" }} + MEEP_APP_ENABLEMENT: {{.MepName}}-meep-app-enablement + {{- else if eq .AppEnablement "global" }} + MEEP_APP_ENABLEMENT: meep-app-enablement + {{- end }} + {{- range .Env}} + {{.}} + {{- end}} + +service: + name: meep-cloud-mosquitto + # type: ClusterIP + # port: 80 + type: NodePort + port: 1883 + NodePort: "" + +ingress: + enabled: true + hosts: + - name: '' + paths: + {{- if .IsMepService }} + - /{{.SandboxName}}/{{.MepName}}/meep-cloud-mosquitto + {{- else }} + - /{{.SandboxName}}/meep-cloud-mosquitto + {{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: {{ .HttpsOnly }} + {{- if .IsMepService }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/{{.MepName}}/meep-cloud-mosquitto(/|$)(.*)$ /$2 break; + {{- else }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/meep-cloud-mosquitto(/|$)(.*)$ /$2 break; + {{- end }} + {{- if .AuthEnabled }} + nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-cloud-mosquitto&sbox={{.SandboxName}}&mep={{.MepName}} + {{- end }} + labels: {} + tls: + +prometheus: + monitor: + enabled: true + port: 9000 + interval: 10s + additionalLabels: {} + relabelings: [] + scrapeTimeout: 5s + +codecov: + enabled: false + location: "/codecov/meep-cloud-mosquitto" + +meepOrigin: core diff --git a/charts/meep-mosquitto/.helmignore b/charts/meep-mosquitto/.helmignore new file mode 100644 index 0000000000000000000000000000000000000000..f0c13194444163d1cba5c67d9e79231a62bc8f44 --- /dev/null +++ b/charts/meep-mosquitto/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/meep-mosquitto/Chart.yaml b/charts/meep-mosquitto/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ae819c7adb1911d474575f02ed96db6f882d8869 --- /dev/null +++ b/charts/meep-mosquitto/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: MEEP MOSQUITTO Service Helm chart for Kubernetes +name: meep-mosquitto +version: 1.0.0 diff --git a/charts/meep-mosquitto/templates/_helpers.tpl b/charts/meep-mosquitto/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..d03e5f8eb99430846b0654914d5754546453a17d --- /dev/null +++ b/charts/meep-mosquitto/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "meep-mosquitto.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "meep-mosquitto.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "meep-mosquitto.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/meep-mosquitto/templates/clusterrolebinding.yaml b/charts/meep-mosquitto/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a3715c23fb509744a268031f03e4246067f49034 --- /dev/null +++ b/charts/meep-mosquitto/templates/clusterrolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{ .Release.Namespace }}:{{ template "meep-mosquitto.fullname" . }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: {{ template "meep-mosquitto.fullname" . }} + namespace: {{ .Release.Namespace }} + \ No newline at end of file diff --git a/charts/meep-mosquitto/templates/codecov-pv.yaml b/charts/meep-mosquitto/templates/codecov-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a38a5c70a3d5f45d36d30875673e6141a943bac1 --- /dev/null +++ b/charts/meep-mosquitto/templates/codecov-pv.yaml @@ -0,0 +1,35 @@ +{{- if .Values.codecov.enabled}} +kind: PersistentVolume +apiVersion: v1 +metadata: + name: meep-mosquitto-codecov-pv +spec: + storageClassName: meep-mosquitto-codecov-sc + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: {{ .Values.codecov.location }} + +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: meep-mosquitto-codecov-sc +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: meep-mosquitto-codecov-pvc +spec: + storageClassName: meep-mosquitto-codecov-sc + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +{{- end}} diff --git a/charts/meep-mosquitto/templates/deployment.yaml b/charts/meep-mosquitto/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..da17d1676add3e0f6a120758b1a6fb80b052e343 --- /dev/null +++ b/charts/meep-mosquitto/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "meep-mosquitto.fullname" . }} + labels: + app: {{ template "meep-mosquitto.name" . }} + chart: {{ template "meep-mosquitto.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + app: {{ template "meep-mosquitto.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "meep-mosquitto.name" . }} + release: {{ .Release.Name }} + meepOrigin: {{ .Values.meepOrigin }} + spec: + serviceAccountName: {{ template "meep-mosquitto.fullname" . }} + {{- if .Values.codecov.enabled}} + volumes: + - name: codecov-storage + persistentVolumeClaim: + claimName: meep-mosquitto-codecov-pvc + {{- end}} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.deployment.port }} + protocol: {{ .Values.deployment.protocol }} + env: + {{- range $key, $value := .Values.image.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add Kubernetes metadata for NodePort retrieval + - name: MEEP_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MEEP_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.codecov.enabled}} + volumeMounts: + - name: codecov-storage + mountPath: /codecov + {{- end}} + terminationGracePeriodSeconds: 5 + initContainers: + {{- range $value := .Values.deployment.dependencies.system }} + - name: init-system-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }}.kube-system ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} + {{- range $value := .Values.deployment.dependencies.namespace }} + - name: init-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }} ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} \ No newline at end of file diff --git a/charts/meep-mosquitto/templates/ingress.yaml b/charts/meep-mosquitto/templates/ingress.yaml new file mode 100755 index 0000000000000000000000000000000000000000..ee4f663b105a59c1de884945dc4d8edbedab3cf7 --- /dev/null +++ b/charts/meep-mosquitto/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := .Values.service.name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + labels: + app: {{ template "meep-mosquitto.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - http: + paths: + {{- range $path := .paths }} + - path: {{ $path }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} + {{- if .name }} + host: {{ .name }} + {{- end }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/meep-mosquitto/templates/monitor.yaml b/charts/meep-mosquitto/templates/monitor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0aa870d1f86ceec794aff5712f873533cfed718f --- /dev/null +++ b/charts/meep-mosquitto/templates/monitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "meep-mosquitto.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "meep-mosquitto.name" . }} + chart: {{ template "meep-mosquitto.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} + {{- if .Values.prometheus.monitor.additionalLabels }} +{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "meep-mosquitto.name" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} +{{- if .Values.prometheus.monitor.relabelings }} + relabelings: +{{ toYaml .Values.prometheus.monitor.relabelings | indent 6 }} +{{- end }} +{{- end }} diff --git a/charts/meep-mosquitto/templates/service.yaml b/charts/meep-mosquitto/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..402794e21d1d2d5d5576b32a9c0fb37575b18a15 --- /dev/null +++ b/charts/meep-mosquitto/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name }} + labels: + app: {{ template "meep-mosquitto.name" . }} + chart: {{ template "meep-mosquitto.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "meep-mosquitto.name" . }} + release: {{ .Release.Name }} + ports: + - name: meep-mosquitto + port: {{ .Values.service.port }} + targetPort: {{ .Values.deployment.port }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.monitor.enabled}} + - name: metrics + port: {{ .Values.prometheus.monitor.port }} + targetPort: {{ .Values.prometheus.monitor.port }} + protocol: TCP + {{- end}} + diff --git a/charts/meep-mosquitto/templates/serviceaccount.yaml b/charts/meep-mosquitto/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4cb50023d3b67c0f0898ef1a2c813011d558cd66 --- /dev/null +++ b/charts/meep-mosquitto/templates/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "meep-mosquitto.fullname" . }} diff --git a/charts/meep-mosquitto/values-template.yaml b/charts/meep-mosquitto/values-template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d9e4995d829b5c71dbc70c046361e0428f478e9c --- /dev/null +++ b/charts/meep-mosquitto/values-template.yaml @@ -0,0 +1,87 @@ +# Default values for meep-mosquitto +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +deployment: + replicas: 1 + port: 1883 + protocol: TCP + dependencies: + system: + {{- if not .IsMepService }} + - kube-dns + {{- end }} + namespace: + +image: + repository: meep-docker-registry:30001/meep-mosquitto + tag: latest + pullPolicy: Always + # entrypointScript: /entrypoint.sh + env: + MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + NR_MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + MEEP_INSTANCE_ID: {{.InstanceId}} + MEEP_SANDBOX_NAME: {{.SandboxName}} + MEEP_HOST_URL: {{.HostUrl}} + {{- if .IsMepService }} + MEEP_MEP_NAME: {{.MepName}} + {{- end }} + {{- if eq .AppEnablement "local" }} + MEEP_APP_ENABLEMENT: {{.MepName}}-meep-app-enablement + {{- else if eq .AppEnablement "global" }} + MEEP_APP_ENABLEMENT: meep-app-enablement + {{- end }} + {{- range .Env}} + {{.}} + {{- end}} + +service: + {{- if .IsMepService }} + name: {{.MepName}}-meep-mosquitto + {{- else }} + name: meep-mosquitto + {{- end }} + type: ClusterIP + port: 1883 + +ingress: + enabled: true + hosts: + - name: '' + paths: + {{- if .IsMepService }} + - /{{.SandboxName}}/{{.MepName}}/meep-mosquitto + {{- else }} + - /{{.SandboxName}}/meep-mosquitto + {{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: {{ .HttpsOnly }} + {{- if .IsMepService }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/{{.MepName}}/meep-mosquitto(/|$)(.*)$ /$2 break; + {{- else }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/meep-mosquitto(/|$)(.*)$ /$2 break; + {{- end }} + {{- if .AuthEnabled }} + nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-mosquitto&sbox={{.SandboxName}}&mep={{.MepName}} + {{- end }} + labels: {} + tls: + +prometheus: + monitor: + enabled: true + port: 9000 + interval: 10s + additionalLabels: {} + relabelings: [] + scrapeTimeout: 5s + +codecov: + enabled: false + location: "/codecov/meep-mosquitto" + +meepOrigin: core diff --git a/charts/meep-tinyiot-in-cse/Chart.yaml b/charts/meep-tinyiot-in-cse/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cf88b448d604217b3684ba4b5d3e0ded88de9ab5 --- /dev/null +++ b/charts/meep-tinyiot-in-cse/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: MEEP tinyIoT IN-CSE Service Helm chart for Kubernetes +name: meep-tinyiot-in-cse +version: 1.0.0 diff --git a/charts/meep-tinyiot-in-cse/templates/_helpers.tpl b/charts/meep-tinyiot-in-cse/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..ae36c314a40afb8ff93466be1dba7f0ca41d0977 --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "meep-tinyiot-in-cse.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "meep-tinyiot-in-cse.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "meep-tinyiot-in-cse.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/meep-tinyiot-in-cse/templates/clusterrolebinding.yaml b/charts/meep-tinyiot-in-cse/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..085f8fbb0c984c2a2b747a050741014ff11b6211 --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/clusterrolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{ .Release.Namespace }}:{{ template "meep-tinyiot-in-cse.fullname" . }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: {{ template "meep-tinyiot-in-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + \ No newline at end of file diff --git a/charts/meep-tinyiot-in-cse/templates/codecov-pv.yaml b/charts/meep-tinyiot-in-cse/templates/codecov-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e0d09e4e2bb06d22f1a4cd2c462a3b2e36d49edc --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/codecov-pv.yaml @@ -0,0 +1,35 @@ +{{- if .Values.codecov.enabled}} +kind: PersistentVolume +apiVersion: v1 +metadata: + name: meep-tinyiot-in-cse-codecov-pv +spec: + storageClassName: meep-tinyiot-in-cse-codecov-sc + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: {{ .Values.codecov.location }} + +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: meep-tinyiot-in-cse-codecov-sc +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: meep-tinyiot-in-cse-codecov-pvc +spec: + storageClassName: meep-tinyiot-in-cse-codecov-sc + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +{{- end}} diff --git a/charts/meep-tinyiot-in-cse/templates/deployment.yaml b/charts/meep-tinyiot-in-cse/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bde3728de0ea9fdd33015bda2b053379e0a032db --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "meep-tinyiot-in-cse.fullname" . }} + labels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + chart: {{ template "meep-tinyiot-in-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + release: {{ .Release.Name }} + meepOrigin: {{ .Values.meepOrigin }} + spec: + serviceAccountName: {{ template "meep-tinyiot-in-cse.fullname" . }} + {{- if .Values.codecov.enabled}} + volumes: + - name: codecov-storage + persistentVolumeClaim: + claimName: meep-tinyiot-in-cse-codecov-pvc + {{- end}} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.deployment.port }} + protocol: {{ .Values.deployment.protocol }} + env: + {{- range $key, $value := .Values.image.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add Kubernetes metadata for NodePort retrieval + - name: MEEP_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MEEP_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.codecov.enabled}} + volumeMounts: + - name: codecov-storage + mountPath: /codecov + {{- end}} + terminationGracePeriodSeconds: 5 + initContainers: + {{- range $value := .Values.deployment.dependencies.system }} + - name: init-system-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }}.kube-system ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} + {{- range $value := .Values.deployment.dependencies.namespace }} + - name: init-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }} ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} \ No newline at end of file diff --git a/charts/meep-tinyiot-in-cse/templates/ingress.yaml b/charts/meep-tinyiot-in-cse/templates/ingress.yaml new file mode 100755 index 0000000000000000000000000000000000000000..7fb3f85dcabb6ae8388c53adb3346934a0e26d15 --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := .Values.service.name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + labels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - http: + paths: + {{- range $path := .paths }} + - path: {{ $path }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} + {{- if .name }} + host: {{ .name }} + {{- end }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/meep-tinyiot-in-cse/templates/monitor.yaml b/charts/meep-tinyiot-in-cse/templates/monitor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9dc6bb91055ebf666dacccdff75f8f3d31be47e6 --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/monitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "meep-tinyiot-in-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + chart: {{ template "meep-tinyiot-in-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} + {{- if .Values.prometheus.monitor.additionalLabels }} +{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} +{{- if .Values.prometheus.monitor.relabelings }} + relabelings: +{{ toYaml .Values.prometheus.monitor.relabelings | indent 6 }} +{{- end }} +{{- end }} diff --git a/charts/meep-tinyiot-in-cse/templates/service.yaml b/charts/meep-tinyiot-in-cse/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d6dec59eb8a236f329c6f153f17d7160b398caee --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name }} + labels: + app: {{ template "meep-tinyiot-in-cse.name" . }} + chart: {{ template "meep-tinyiot-in-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "meep-tinyiot-in-cse.name" . }} + release: {{ .Release.Name }} + ports: + - name: tinyiot + port: {{ .Values.service.port }} + targetPort: {{ .Values.deployment.port }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.monitor.enabled}} + - name: metrics + port: {{ .Values.prometheus.monitor.port }} + targetPort: {{ .Values.prometheus.monitor.port }} + protocol: TCP + {{- end}} + diff --git a/charts/meep-tinyiot-in-cse/templates/serviceaccount.yaml b/charts/meep-tinyiot-in-cse/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..aba9d487372dddccc41b2d6b24466108a6060366 --- /dev/null +++ b/charts/meep-tinyiot-in-cse/templates/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "meep-tinyiot-in-cse.fullname" . }} diff --git a/charts/meep-tinyiot-in-cse/values-template.yaml b/charts/meep-tinyiot-in-cse/values-template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..13350e98af181928c084bd4cb3e084d798988a6f --- /dev/null +++ b/charts/meep-tinyiot-in-cse/values-template.yaml @@ -0,0 +1,99 @@ +# Default values for meep-tinyiot-in-cse +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +deployment: + replicas: 1 + port: 3002 + protocol: TCP + dependencies: + system: + {{- if not .IsMepService }} + - kube-dns + {{- end }} + namespace: + +image: + repository: meep-docker-registry:30001/meep-tinyiot-in-cse + tag: latest + pullPolicy: Always + # entrypointScript: /entrypoint.sh + env: + MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + NR_MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + MEEP_INSTANCE_ID: {{.InstanceId}} + MEEP_SANDBOX_NAME: {{.SandboxName}} + MEEP_SVC_PATH: /tinyiot-in-cse + MEEP_HOST_URL: {{.HostUrl}} + # MEEP_SERVER_PORT: 3002 + # MQTT_HOST: meep-mosquitto + # MQTT_PORT: 1883 + SERVER_TYPE: IN_CSE + SERVER_PORT: "3002" + CSE_BASE_NAME: "TinyIoT" + CSE_BASE_RI: "tinyiot" + MQTT_HOST: "meep-mosquitto" + MQTT_PORT: 1883 + MQTT_USERNAME: "test" + MQTT_PASSWORD: "mqttq" + MEEP_TOPIC: {{.SandboxName}}/tinyiot-in-cse + {{- if .IsMepService }} + MEEP_MEP_NAME: {{.MepName}} + {{- end }} + {{- if eq .AppEnablement "local" }} + MEEP_APP_ENABLEMENT: {{.MepName}}-meep-app-enablement + {{- else if eq .AppEnablement "global" }} + MEEP_APP_ENABLEMENT: meep-app-enablement + {{- end }} + {{- range .Env}} + {{.}} + {{- end}} + +service: + {{- if .IsMepService }} + name: {{.MepName}}-meep-tinyiot-in-cse + {{- else }} + name: meep-tinyiot-in-cse + {{- end }} + type: ClusterIP + port: 3002 +ingress: + enabled: true + hosts: + - name: '' + paths: + {{- if .IsMepService }} + - /{{.SandboxName}}/{{.MepName}}/tinyiot-in-cse + {{- else }} + - /{{.SandboxName}}/tinyiot-in-cse + {{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: {{ .HttpsOnly }} + {{- if .IsMepService }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/{{.MepName}}/tinyiot-in-cse(/|$)(.*)$ /$2 break; + {{- else }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/tinyiot-in-cse(/|$)(.*)$ /$2 break; + {{- end }} + {{- if .AuthEnabled }} + nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-tinyiot-in-cse&sandbox={{.SandboxName}}&mep={{.MepName}} + {{- end }} + labels: {} + tls: + +prometheus: + monitor: + enabled: true + port: 9000 + interval: 10s + additionalLabels: {} + relabelings: [] + scrapeTimeout: 5s + +codecov: + enabled: false + location: "/codecov/meep-tinyiot-in-cse" + +meepOrigin: core diff --git a/charts/meep-tinyiot-mn-cse/Chart.yaml b/charts/meep-tinyiot-mn-cse/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fd875f6fa6e43afdd701c9280d772320c2fcc943 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: MEEP tinyIoT MN-CSE Service Helm chart for Kubernetes +name: meep-tinyiot-mn-cse +version: 1.0.0 diff --git a/charts/meep-tinyiot-mn-cse/templates/_helpers.tpl b/charts/meep-tinyiot-mn-cse/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..34ea3a95540d418058e407107c9671e9b6f50a0d --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/_helpers.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "meep-tinyiot-mn-cse.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "meep-tinyiot-mn-cse.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "meep-tinyiot-mn-cse.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/meep-tinyiot-mn-cse/templates/clusterrolebinding.yaml b/charts/meep-tinyiot-mn-cse/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8ba0428ee115c687b438957ea73424f806211171 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/clusterrolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "{{ .Release.Namespace }}:{{ template "meep-tinyiot-mn-cse.fullname" . }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: {{ template "meep-tinyiot-mn-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + \ No newline at end of file diff --git a/charts/meep-tinyiot-mn-cse/templates/codecov-pv.yaml b/charts/meep-tinyiot-mn-cse/templates/codecov-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..29bda937f8f749bbe27716f8f8976d942ddfce58 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/codecov-pv.yaml @@ -0,0 +1,35 @@ +{{- if .Values.codecov.enabled}} +kind: PersistentVolume +apiVersion: v1 +metadata: + name: meep-tinyiot-mn-cse-codecov-pv +spec: + storageClassName: meep-tinyiot-mn-cse-codecov-sc + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: {{ .Values.codecov.location }} + +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: meep-tinyiot-mn-cse-codecov-sc +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: meep-tinyiot-mn-cse-codecov-pvc +spec: + storageClassName: meep-tinyiot-mn-cse-codecov-sc + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +{{- end}} diff --git a/charts/meep-tinyiot-mn-cse/templates/deployment.yaml b/charts/meep-tinyiot-mn-cse/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..341e244c7195310d68fcb58f6315816408ba71e3 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "meep-tinyiot-mn-cse.fullname" . }} + labels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + chart: {{ template "meep-tinyiot-mn-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + release: {{ .Release.Name }} + meepOrigin: {{ .Values.meepOrigin }} + spec: + serviceAccountName: {{ template "meep-tinyiot-mn-cse.fullname" . }} + {{- if .Values.codecov.enabled}} + volumes: + - name: codecov-storage + persistentVolumeClaim: + claimName: meep-tinyiot-mn-cse-codecov-pvc + {{- end}} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.deployment.port }} + protocol: {{ .Values.deployment.protocol }} + env: + {{- range $key, $value := .Values.image.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add Kubernetes metadata for NodePort retrieval + - name: MEEP_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MEEP_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.codecov.enabled}} + volumeMounts: + - name: codecov-storage + mountPath: /codecov + {{- end}} + terminationGracePeriodSeconds: 5 + initContainers: + {{- range $value := .Values.deployment.dependencies.system }} + - name: init-system-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }}.kube-system ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} + {{- range $value := .Values.deployment.dependencies.namespace }} + - name: init-{{ $value }} + image: busybox:1.28 + imagePullPolicy: IfNotPresent + command: ['sh', '-c', 'until nslookup {{ $value }} ; do echo waiting for {{ $value }}; sleep 0.25; done;'] + {{- end}} \ No newline at end of file diff --git a/charts/meep-tinyiot-mn-cse/templates/ingress.yaml b/charts/meep-tinyiot-mn-cse/templates/ingress.yaml new file mode 100755 index 0000000000000000000000000000000000000000..46999242e665537fd8105772e89d16a3d6212fa8 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := .Values.service.name -}} +{{- $servicePort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + labels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels | indent 4 }} +{{- end }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + rules: + {{- range .Values.ingress.hosts }} + - http: + paths: + {{- range $path := .paths }} + - path: {{ $path }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} + {{- if .name }} + host: {{ .name }} + {{- end }} + {{- end -}} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} diff --git a/charts/meep-tinyiot-mn-cse/templates/monitor.yaml b/charts/meep-tinyiot-mn-cse/templates/monitor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4fc7f32b9aa5f61eb707aae016d75c4fa1195d89 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/monitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "meep-tinyiot-mn-cse.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + chart: {{ template "meep-tinyiot-mn-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} + {{- if .Values.prometheus.monitor.additionalLabels }} +{{ toYaml .Values.prometheus.monitor.additionalLabels | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + release: {{ .Release.Name }} + endpoints: + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} +{{- if .Values.prometheus.monitor.relabelings }} + relabelings: +{{ toYaml .Values.prometheus.monitor.relabelings | indent 6 }} +{{- end }} +{{- end }} diff --git a/charts/meep-tinyiot-mn-cse/templates/service.yaml b/charts/meep-tinyiot-mn-cse/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..69b3158183f98dd97530265a51d8a91ce0285e1b --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/service.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.service.name }} + labels: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + chart: {{ template "meep-tinyiot-mn-cse.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + meepOrigin: {{ .Values.meepOrigin }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "meep-tinyiot-mn-cse.name" . }} + release: {{ .Release.Name }} + ports: + - name: tinyiot + port: {{ .Values.service.port }} + targetPort: {{ .Values.deployment.port }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.monitor.enabled}} + - name: metrics + port: {{ .Values.prometheus.monitor.port }} + targetPort: {{ .Values.prometheus.monitor.port }} + protocol: TCP + {{- end}} + diff --git a/charts/meep-tinyiot-mn-cse/templates/serviceaccount.yaml b/charts/meep-tinyiot-mn-cse/templates/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9230630d730e8493aa5656bca7a4698d72908f94 --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/templates/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "meep-tinyiot-mn-cse.fullname" . }} diff --git a/charts/meep-tinyiot-mn-cse/values-template.yaml b/charts/meep-tinyiot-mn-cse/values-template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8a3563ac277835326e834fe1ea7bc7325a6680fa --- /dev/null +++ b/charts/meep-tinyiot-mn-cse/values-template.yaml @@ -0,0 +1,87 @@ +# Default values for meep-tinyiot-mn-cse +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +deployment: + replicas: 1 + port: 3005 + protocol: TCP + dependencies: + system: + {{- if not .IsMepService }} + - kube-dns + {{- end }} + namespace: + +image: + repository: meep-docker-registry:30001/meep-tinyiot-mn-cse + tag: latest + pullPolicy: Always + # entrypointScript: /entrypoint.sh + env: + MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + NR_MEAS_REP_UE_PERIODIC_TRIGGER_INTERVAL: 1s + MEEP_INSTANCE_ID: {{.InstanceId}} + MEEP_SANDBOX_NAME: {{.SandboxName}} + MEEP_SVC_PATH: /tinyiot-mn-cse + MEEP_HOST_URL: {{.HostUrl}} + {{- if .IsMepService }} + MEEP_MEP_NAME: {{.MepName}} + {{- end }} + {{- if eq .AppEnablement "local" }} + MEEP_APP_ENABLEMENT: {{.MepName}}-meep-app-enablement + {{- else if eq .AppEnablement "global" }} + MEEP_APP_ENABLEMENT: meep-app-enablement + {{- end }} + {{- range .Env}} + {{.}} + {{- end}} + +service: + {{- if .IsMepService }} + name: {{.MepName}}-meep-tinyiot-mn-cse + {{- else }} + name: meep-tinyiot-mn-cse + {{- end }} + type: ClusterIP + port: 3005 +ingress: + enabled: true + hosts: + - name: '' + paths: + {{- if .IsMepService }} + - /{{.SandboxName}}/{{.MepName}}/tinyiot-mn-cse + {{- else }} + - /{{.SandboxName}}/tinyiot-mn-cse + {{- end }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/force-ssl-redirect: {{ .HttpsOnly }} + {{- if .IsMepService }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/{{.MepName}}/tinyiot-mn-cse(/|$)(.*)$ /$2 break; + {{- else }} + nginx.ingress.kubernetes.io/configuration-snippet: | + rewrite ^/{{.SandboxName}}/tinyiot-mn-cse(/|$)(.*)$ /$2 break; + {{- end }} + {{- if .AuthEnabled }} + nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-tinyiot-mn-cse&sandbox={{.SandboxName}}&mep={{.MepName}} + {{- end }} + labels: {} + tls: + +prometheus: + monitor: + enabled: true + port: 9000 + interval: 10s + additionalLabels: {} + relabelings: [] + scrapeTimeout: 5s + +codecov: + enabled: false + location: "/codecov/meep-tinyiot-mn-cse" + +meepOrigin: core diff --git a/charts/meep-vis/values-template.yaml b/charts/meep-vis/values-template.yaml index 6a6eaafaf3882f961a848476b3e1fd43f0adb7e7..de1f4b8be106244a20fe560ab904458da03ef358 100644 --- a/charts/meep-vis/values-template.yaml +++ b/charts/meep-vis/values-template.yaml @@ -22,7 +22,7 @@ image: MEEP_SANDBOX_NAME: {{.SandboxName}} MEEP_SVC_PATH: /vis/v2 MEEP_HOST_URL: {{.HostUrl}} - MEEP_BROKER: mqtt://meep-mosquitto:9001 + MEEP_BROKER: mqtt://meep-mosquitto:1883 MEEP_TOPIC: 3gpp/v2x/obu MEEP_POA_LIST: poa-5g1 {{- if .IsMepService }} diff --git a/charts/postgis/values.yaml b/charts/postgis/values.yaml index 7018fb55a4c52b5884f7c25c12464907b15eb2b1..29e0a4f58e06bde4bdab8de6acc494b3a4a5a0ad 100644 --- a/charts/postgis/values.yaml +++ b/charts/postgis/values.yaml @@ -14,7 +14,7 @@ global: image: registry: docker.io repository: postgis/postgis - tag: 12-3.0 + tag: latest ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -542,7 +542,7 @@ metrics: image: registry: docker.io repository: bitnami/postgres-exporter - tag: 0.8.0-debian-10-r106 + tag: latest pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. diff --git a/charts/redis/values-production.yaml b/charts/redis/values-production.yaml index 11f6ca759867ec8ea42de5985743acde6abfc02d..23103c85f56135edf94f12d8c6c3d6e50a7c4a87 100644 --- a/charts/redis/values-production.yaml +++ b/charts/redis/values-production.yaml @@ -20,7 +20,7 @@ image: ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links ## # tag: 6.0.9-debian-10-r66 - tag: 1.0.7 + tag: latest ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -62,7 +62,7 @@ sentinel: ## Bitnami Redis(TM) image tag ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links ## - tag: 6.0.9-debian-10-r66 + tag: latest ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -677,7 +677,7 @@ metrics: image: registry: docker.io repository: bitnami/redis-exporter - tag: 1.15.0-debian-10-r8 + tag: latest pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. diff --git a/charts/redis/values.yaml b/charts/redis/values.yaml index 3eaa127aafca1ae578cd768e23a69fa45c69dbfc..fdb649cd6e2f0d325670b6673c5aa497f35f1b18 100644 --- a/charts/redis/values.yaml +++ b/charts/redis/values.yaml @@ -18,7 +18,7 @@ image: ## Bitnami Redis(TM) image tag ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links ## - tag: 6.0.9-debian-10-r66 + tag: latest ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -60,7 +60,7 @@ sentinel: ## Bitnami Redis(TM) image tag ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links ## - tag: 6.0.9-debian-10-r66 + tag: latest ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -696,7 +696,7 @@ metrics: image: registry: docker.io repository: bitnami/redis-exporter - tag: 1.15.0-debian-10-r8 + tag: latest pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. diff --git a/docs/meep-sss/SensorDataSubscriptionApi.md b/docs/meep-sss/SensorDataSubscriptionApi.md index 2f3fca78f1d996be2ce856783eb87ca2227c6f13..ce4d3bc14e56a307959bd9469cfc8e6cd5f7ad74 100644 --- a/docs/meep-sss/SensorDataSubscriptionApi.md +++ b/docs/meep-sss/SensorDataSubscriptionApi.md @@ -133,7 +133,7 @@ This method shall support the URI query parameters, request and response data st Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. - **body** | [**StatusDataSubscriptionIdBody**](StatusDataSubscriptionIdBody.md)| New SensorDataSubscription is included as entity body of the request | + **body** | [**SensorDataSubscriptionIdBody**](SensorDataSubscriptionIdBody.md)| New SensorDataSubscription is included as entity body of the request | **subscriptionId** | **string**| Unique identifiers of a subscription | ### Return type diff --git a/docs/meep-sss/StatusDataSubscriptionIdBody.md b/docs/meep-sss/StatusDataSubscriptionIdBody.md index 70a2b2e720cbd4db57155dfac153e1731ec911dc..ed1b52db2368fc0315aa62fac5da6d680ebb7556 100644 --- a/docs/meep-sss/StatusDataSubscriptionIdBody.md +++ b/docs/meep-sss/StatusDataSubscriptionIdBody.md @@ -1,4 +1,4 @@ -# StatusDataSubscriptionIdBody +# SensorDataSubscriptionIdBody ## Properties Name | Type | Description | Notes diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4fd07cff8bacde5f4a36ca6cba242e353ec21758 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,13 @@ + +# MEC application examples + +The examples folder contains a set of MEC application examples relative to different MEC topics. + +- [demo1](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/tree/master/examples/demo1?ref_type=heads#demo1) showcases platform capabilities using iperf application; +- [demo2](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/tree/master/examples/demo2?ref_type=heads#demo2) is similar than demo1, using user charts to deploy its components instead of using dynamic chart generation; +- [demo3](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/tree/master/examples/demo3?ref_type=heads#demo3) demonstrates the usage of mobility services and MEC application handover (MEC 021); +- [demo4](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/tree/master/examples/demo4-ue?ref_type=heads#demo4) demonstrates the usage of MEC 016 +- [demo6]() demonstrates the MEC Sandbox API usage, MEC 013, MEC 030 and MEC Federation; +- [demo7](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/tree/master/examples/demo7?ref_type=heads#demo7) is about MEC 030 & ETSI ITS; +- [demo8](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/blob/master/examples/demo8/README.md?ref_type=heads) demonstrates the usage of MEC profile for CAPIF; +- [demo9](under construction) demonstrates the usage of MEC 033 & MEC 046 and MEC interworking wuth oneM2M. diff --git a/examples/demo10/Dockerfile b/examples/demo10/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9ef4d1b42004f68d9649f074de2acab4e947d4c2 --- /dev/null +++ b/examples/demo10/Dockerfile @@ -0,0 +1,26 @@ +# Copyright (c) 2022 The AdvantEDGE Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang + + +# Some ENV variables +ENV SERVICE_NAME = "demo10" + +COPY ./demo10 /demo10 +COPY entrypoint.sh / + +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/examples/demo10/MEC/Get_App_Instances.go b/examples/demo10/MEC/Get_App_Instances.go new file mode 100644 index 0000000000000000000000000000000000000000..f2010fdb5355d8222c2ee66fd79c11a569ac14c3 --- /dev/null +++ b/examples/demo10/MEC/Get_App_Instances.go @@ -0,0 +1,45 @@ +package mec + +import ( + model "demo10/Model" + utils "demo10/utils" + "encoding/json" + "fmt" + "io" + "net/http" +) + +// GetAppInstances retrieves all MEC Application Instances from the platform +// +// This function queries the MEC platform's application management API to obtain +// a list of all available application instances, including their IDs, names, and node names. +// +// Reference: ETSI GS MEC 011 - MEC Application Lifecycle Management API +// +// @param url The URL endpoint for querying application instances +// (e.g., /sandbox-ctrl/v1/applications) +// @return []model.AppInstanceID Array of application instance information +// @return error Returns nil on success, or an error if the request fails +func GetAppInstances(url string) ([]model.AppInstanceID, error) { + resp, err := utils.SendMECRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to get MEC app instances: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get MEC App Instance ID: received status code %d", resp.StatusCode) + } + + // From the response body, extract all App Instance IDs + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + //log.Println("Response body:", string(body)) + + var allAppInstances []model.AppInstanceID + if err := json.Unmarshal(body, &allAppInstances); err != nil { + return nil, fmt.Errorf("failed to unmarshal response body: %v", err) + } + return allAppInstances, nil +} diff --git a/examples/demo10/MEC/ams.go b/examples/demo10/MEC/ams.go new file mode 100644 index 0000000000000000000000000000000000000000..183e47990e8b3f8afb3ae69f654de4b9717b75e0 --- /dev/null +++ b/examples/demo10/MEC/ams.go @@ -0,0 +1,420 @@ +package mec + +import ( + model "demo10/Model" + utils "demo10/utils" + "encoding/json" + "fmt" + "log" + "net/http" + "regexp" +) + +// CreateAMSRegistration creates a MEC Application Mobility Service (AMS) registration +// +// This function creates an AMS registration for a given application instance ID. +// The registration includes device information if provided, which is used to +// associate the device with the application instance for mobility management. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @param url The URL endpoint for AMS registration (e.g., /amsi/v1/app_mobility_services) +// @param app_instance_id The MEC Application Instance ID to register +// @param device_info Optional device information for the registration (can be nil) +// @return *model.RegistrationInfo The registration information returned by the MEC platform +// @return error Returns nil on success, or an error if the registration fails +func CreateAMSRegistration(url string, app_instance_id string, device_info *model.RegistrationInfoDeviceInformation) (*model.RegistrationInfo, error) { + payload := &model.RegistrationInfo{} + if device_info != nil { + payload = &model.RegistrationInfo{ + ServiceConsumerId: &model.RegistrationInfoServiceConsumerId{ + AppInstanceId: app_instance_id, + }, + DeviceInformation: []model.RegistrationInfoDeviceInformation{*device_info}, + } + } else { + payload = &model.RegistrationInfo{ + ServiceConsumerId: &model.RegistrationInfoServiceConsumerId{ + AppInstanceId: app_instance_id, + }, + } + } + resp, err := utils.SendMECRequest("POST", url, payload) + if err != nil { + return nil, fmt.Errorf("failed to create AMS registration: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("failed to create AMS registration: received status code %d", resp.StatusCode) + } + + var registrationInfo model.RegistrationInfo + if err := json.NewDecoder(resp.Body).Decode(®istrationInfo); err != nil { + return nil, fmt.Errorf("failed to decode AMS registration response: %v", err) + } + + return ®istrationInfo, nil +} + +// CreateAMSSubscription creates a MEC Application Mobility Service (AMS) subscription +// +// This function creates a subscription for mobility procedure notifications. +// The subscription allows the application to receive notifications when mobility +// events occur, such as when the application instance moves to a different MEC host. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @param url The URL endpoint for AMS subscription (e.g., /amsi/v1/subscriptions) +// @param app_instance_id The MEC Application Instance ID to subscribe to +// @param callbackURL The callback URL where mobility notifications will be sent +// @param associateId Optional associate ID (e.g., UE IP address) for filtering notifications +// @return *model.InlineSubscription The subscription information returned by the MEC platform +// @return error Returns nil on success, or an error if the subscription creation fails +func CreateAMSSubscription(url string, app_instance_id string, callbackURL string, associateId *model.AssociateId) (*model.InlineSubscription, error) { + payload := &model.InlineSubscription{} + if associateId != nil { + payload = &model.InlineSubscription{ + SubscriptionType: "MobilityProcedureSubscription", + FilterCriteria: &model.MobilityProcedureSubscriptionFilterCriteria{ + AppInstanceId: app_instance_id, + AssociateId: []model.AssociateId{*associateId}, + }, + CallbackReference: callbackURL, + } + } else { + payload = &model.InlineSubscription{ + SubscriptionType: "MobilityProcedureSubscription", + FilterCriteria: &model.MobilityProcedureSubscriptionFilterCriteria{ + AppInstanceId: app_instance_id, + }, + // TODO: Update callback reference URL as needed + CallbackReference: callbackURL, + } + } + + resp, err := utils.SendMECRequest("POST", url, payload) + if err != nil { + return nil, fmt.Errorf("failed to create AMS subscription: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("failed to create AMS subscription: received status code %d", resp.StatusCode) + } + + var subscriptionInfo model.InlineSubscription + if err := json.NewDecoder(resp.Body).Decode(&subscriptionInfo); err != nil { + return nil, fmt.Errorf("failed to decode AMS subscription response: %v", err) + } + + return &subscriptionInfo, nil +} + +// AMSNotificationHandler handles incoming AMS mobility procedure notifications +// +// This HTTP handler processes POST requests containing AMS notifications. +// It extracts the target application instance ID from the notification payload, +// which indicates that the application should migrate to a new MEC host. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @param w HTTP response writer +// @param r HTTP request containing the AMS notification +// @return string The target application instance ID extracted from the notification +// @return error Returns nil on success, or an error if the notification cannot be processed +func AMSNotificationHandler(w http.ResponseWriter, r *http.Request) (string, error) { + if r.Method != http.MethodPost { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + return "", fmt.Errorf("invalid request method") + } + + var notification map[string]interface{} + err := json.NewDecoder(r.Body).Decode(¬ification) + if err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return "", fmt.Errorf("failed to decode request body: %w", err) + } + log.Printf("Received AMS notification: %+v", notification) + w.WriteHeader(http.StatusNoContent) + // Extract target app instance ID from the notification + var targetAppInstanceID string + if targetAppInfo, ok := notification["targetAppInfo"].(map[string]interface{}); ok { + if appInstanceID, ok := targetAppInfo["appInstanceId"].(string); ok { + targetAppInstanceID = appInstanceID + log.Printf("Target App Instance ID: %s", targetAppInstanceID) + } + } + if targetAppInstanceID == "" { + log.Println("Target App Instance ID not found in notification") + } + return targetAppInstanceID, nil +} + +// RegistrationAndSubscriptionHandler creates AMS registrations and subscriptions for multiple app instances +// +// This function iterates through matching application instances and creates: +// - AMS registration for each app instance (with device info for the first instance) +// - AMS subscription for each app instance to receive mobility notifications +// +// It collects platform information (AMS service ID, subscription ID, node name) for each +// app instance and returns it in a structured format. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @param matchingAppInstances Array of matching MEC Application Instance IDs +// @param platformURL Base URL of the MEC platform +// @param sandbox_name Name of the MEC sandbox +// @param callbackURL URL where AMS notifications will be sent +// @param currentAppInstanceId Pointer to store the current app instance ID (first instance) +// @param deviceInfo Device information for registration (used for first instance) +// @param associateId Associate ID for subscription filtering (used for first instance) +// @return []map[string]model.PlatformInfo Array of platform information maps keyed by app instance ID +// @return error Returns nil on success, or an error if registration/subscription fails +func RegistrationAndSubscriptionHandler(matchingAppInstances []model.AppInstanceID, platformURL string, + sandbox_name string, callbackURL string, currentAppInstanceId *string, + deviceInfo *model.RegistrationInfoDeviceInformation, associateId *model.AssociateId) ([]map[string]model.PlatformInfo, error) { + var platformDetails []map[string]model.PlatformInfo + + for i, appInstance := range matchingAppInstances { + log.Printf("Creating AMS registration for App Instance ID: %s, Name: %s", appInstance.ID, appInstance.Name) + amsURL := fmt.Sprintf("%s/%s/mep1/amsi/v1/app_mobility_services", platformURL, sandbox_name) + var resp *model.RegistrationInfo + var err error + if i == 0 { + *currentAppInstanceId = appInstance.ID + resp, err = CreateAMSRegistration(amsURL, appInstance.ID, deviceInfo) + if err != nil { + return nil, fmt.Errorf("failed to create AMS registration: %v", err) + } else { + log.Printf("AMS registration created successfully: %v", resp) + } + } else { + resp, err = CreateAMSRegistration(amsURL, appInstance.ID, nil) + if err != nil { + return nil, fmt.Errorf("failed to create AMS registration: %v", err) + } else { + log.Printf("AMS registration created successfully: %v", resp) + } + } + amsSubURL := fmt.Sprintf("%s/%s/mep1/amsi/v1/subscriptions", platformURL, sandbox_name) + sub_resp := &model.InlineSubscription{} + if i == 0 { + sub_resp, err = CreateAMSSubscription(amsSubURL, appInstance.ID, callbackURL, associateId) + if err != nil { + return nil, fmt.Errorf("failed to create AMS subscription: %v", err) + } else { + log.Printf("AMS subscription created successfully: %v", sub_resp) + } + } else { + sub_resp, err = CreateAMSSubscription(amsSubURL, appInstance.ID, callbackURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create AMS subscription: %v", err) + } else { + log.Printf("AMS subscription created successfully: %v", sub_resp) + } + } + // Extract subscription ID from href using regex + subID := "" + if sub_resp.Links.Self.Href != "" { + re := regexp.MustCompile(`subscriptions/(.*)`) + matches := re.FindStringSubmatch(sub_resp.Links.Self.Href) + if len(matches) > 1 { + subID = matches[1] + } + } + platformInfo := model.PlatformInfo{ + AmsServiceID: resp.AppMobilityServiceId, + AppInstanceID: appInstance.ID, + AmsSubscriptionID: subID, + NodeName: appInstance.NodeName, + } + platformDetails = append(platformDetails, map[string]model.PlatformInfo{ + appInstance.ID: platformInfo, + }) + } + + return platformDetails, nil +} + +// PutAMSRegistrationInfo updates AMS registration information for mobility handover +// +// This function updates the AMS registration for both the current and target application +// instances during a mobility handover procedure. It updates the context transfer state +// to reflect the mobility status. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @param url The base URL endpoint for AMS registration updates +// @param targetappinstance The target application instance ID for the handover +// @param platformDetails Array of platform information maps containing AMS service IDs +// @param current_app_instance Pointer to the current application instance ID +// @param associateID Associate ID (e.g., UE IP address) for the registration +// @return error Returns nil on success, or an error if the update fails +func PutAMSRegistrationInfo(url string, targetappinstance string, platformDetails []map[string]model.PlatformInfo, + current_app_instance *string, associateID *model.AssociateId) error { + if len(platformDetails) == 0 { + return fmt.Errorf("platformDetails is empty") + } + if targetappinstance == "" { + log.Println("Initializing AMS registration info...") + } + + // Find the PlatformInfo for the current app instance by iterating through the slice + var currentPlatformInfo *model.PlatformInfo + for _, detail := range platformDetails { + if info, exists := detail[*current_app_instance]; exists { + currentPlatformInfo = &info + break // Found it, no need to continue + } + } + payload := model.RegistrationInfo{ + AppMobilityServiceId: currentPlatformInfo.AmsServiceID, + DeviceInformation: []model.RegistrationInfoDeviceInformation{ + { + AssociateId: associateID, + AppMobilityServiceLevel: &[]model.AppMobilityServiceLevel{"APP_MOBILITY_WITHOUT_CONFIRMATION"}[0], + ContextTransferState: &[]model.ContextTransferState{"USER_CONTEXT_TRANSFER_COMPLETED"}[0], + }, + }, + ServiceConsumerId: &model.RegistrationInfoServiceConsumerId{ + AppInstanceId: *current_app_instance, + }, + } + req_url := fmt.Sprintf("%s/%s", url, currentPlatformInfo.AmsServiceID) + resp, err := utils.SendMECRequest("PUT", req_url, payload) + if err != nil { + return fmt.Errorf("failed to update AMS registration info: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to update AMS registration info: received status code %d", resp.StatusCode) + } + log.Println("AMS registration info updated successfully") + + // find the PlatformInfo for the target app instance + var targetPlatformInfo *model.PlatformInfo + for _, detail := range platformDetails { + if info, exists := detail[targetappinstance]; exists { + targetPlatformInfo = &info + break // Found it, no need to continue + } + } + payload = model.RegistrationInfo{ + AppMobilityServiceId: targetPlatformInfo.AmsServiceID, + DeviceInformation: []model.RegistrationInfoDeviceInformation{ + { + AssociateId: associateID, + AppMobilityServiceLevel: &[]model.AppMobilityServiceLevel{"APP_MOBILITY_WITHOUT_CONFIRMATION"}[0], + ContextTransferState: &[]model.ContextTransferState{"NOT_TRANSFERRED"}[0], + }, + }, + ServiceConsumerId: &model.RegistrationInfoServiceConsumerId{ + AppInstanceId: targetappinstance, + }, + } + req_url = fmt.Sprintf("%s/%s", url, targetPlatformInfo.AmsServiceID) + resp, err = utils.SendMECRequest("PUT", req_url, payload) + if err != nil { + return fmt.Errorf("failed to update AMS registration info for target app instance: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to update AMS registration info for target app instance: received status code %d", resp.StatusCode) + } + + log.Println("AMS registration info for target app instance updated successfully") + + return nil +} + +// PUTAMSSubscriptionInfo updates AMS subscription information for mobility handover +// +// This function updates the AMS subscription for both the current and target application +// instances during a mobility handover. It updates the mobility status filter criteria +// to reflect the current state of the handover procedure. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @param url The base URL endpoint for AMS subscription updates +// @param targetappinstance The target application instance ID for the handover +// @param platformDetails Array of platform information maps containing subscription IDs +// @param current_app_instance Pointer to the current application instance ID +// @param call_back_url The callback URL for receiving mobility notifications +// @param associateid Associate ID (e.g., UE IP address) for subscription filtering +// @return error Returns nil on success, or an error if the update fails +func PUTAMSSubscriptionInfo(url string, targetappinstance string, platformDetails []map[string]model.PlatformInfo, + current_app_instance *string, call_back_url string, associateid *model.AssociateId) error { + if len(platformDetails) == 0 { + return fmt.Errorf("platformDetails is empty") + } + if targetappinstance == "" { + log.Println("Initializing AMS subscription info...") + } + + // Find the PlatformInfo for the current app instance by iterating through the slice + var currentPlatformInfo *model.PlatformInfo + for _, detail := range platformDetails { + if info, exists := detail[*current_app_instance]; exists { + currentPlatformInfo = &info + break // Found it, no need to continue + } + } + + payload := model.InlineSubscription{ + SubscriptionType: "MobilityProcedureSubscription", + Links: &model.MobilityProcedureSubscriptionLinks{ + Self: &model.LinkType{ + Href: fmt.Sprintf("%s/%s", url, currentPlatformInfo.AmsSubscriptionID), + }, + }, + CallbackReference: call_back_url, + FilterCriteria: &model.MobilityProcedureSubscriptionFilterCriteria{ + AppInstanceId: *current_app_instance, + AssociateId: []model.AssociateId{*associateid}, + MobilityStatus: []model.MobilityStatus{"INTERHOST_MOVEOUT_COMPLETED"}, + }, + } + resp, err := utils.SendMECRequest("PUT", fmt.Sprintf("%s/%s", url, currentPlatformInfo.AmsSubscriptionID), payload) + if err != nil { + return fmt.Errorf("failed to update AMS subscription info: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to update AMS subscription info: received status code %d", resp.StatusCode) + } + log.Println("AMS subscription info updated successfully") + + // find the PlatformInfo for the target app instance + var targetPlatformInfo *model.PlatformInfo + for _, detail := range platformDetails { + if info, exists := detail[targetappinstance]; exists { + targetPlatformInfo = &info + break // Found it, no need to continue + } + } + + payload = model.InlineSubscription{ + SubscriptionType: "MobilityProcedureSubscription", + Links: &model.MobilityProcedureSubscriptionLinks{ + Self: &model.LinkType{ + Href: fmt.Sprintf("%s/%s", url, targetPlatformInfo.AmsSubscriptionID), + }, + }, + CallbackReference: call_back_url, + FilterCriteria: &model.MobilityProcedureSubscriptionFilterCriteria{ + AppInstanceId: targetappinstance, + AssociateId: []model.AssociateId{*associateid}, + MobilityStatus: []model.MobilityStatus{"INTERHOST_MOVEOUT_TRIGGERED"}, + }, + } + resp, err = utils.SendMECRequest("PUT", fmt.Sprintf("%s/%s", url, targetPlatformInfo.AmsSubscriptionID), payload) + if err != nil { + return fmt.Errorf("failed to update AMS subscription info for target app instance: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to update AMS subscription info for target app instance: received status code %d", resp.StatusCode) + } + + log.Println("AMS subscription info for target app instance updated successfully") + return nil +} diff --git a/examples/demo10/MEC/iot_api.go b/examples/demo10/MEC/iot_api.go new file mode 100644 index 0000000000000000000000000000000000000000..104e511bc1ca4357fc416dd2282a65f3710fbd1d --- /dev/null +++ b/examples/demo10/MEC/iot_api.go @@ -0,0 +1,36 @@ +package mec + +import ( + utils "demo10/utils" + "encoding/json" + "fmt" + "net/http" +) + +// GETIotPlatformInfo retrieves IoT platform information from the MEC platform +// +// This function queries the MEC platform's IoT API to obtain information about +// registered IoT platforms, including their endpoints and connection details. +// +// Reference: ETSI GS MEC 040 - MEC IoT API +// +// @param url The URL endpoint for querying registered IoT platforms +// (e.g., /iots/v1/registered_iot_platforms) +// @return []map[string]interface{} Array of IoT platform information maps +// @return error Returns nil on success, or an error if the request fails +func GETIotPlatformInfo(url string) ([]map[string]interface{}, error) { + resp, err := utils.SendMECRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to get IoT platform info: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get IoT platform info: received status code %d", resp.StatusCode) + } + + var platformInfo []map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&platformInfo); err != nil { + return nil, fmt.Errorf("failed to decode IoT platform info: %v", err) + } + return platformInfo, nil +} diff --git a/examples/demo10/MEC/match_for_target_app_instance.go b/examples/demo10/MEC/match_for_target_app_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..478bc31e2bf97a906697040cc7392970f0573588 --- /dev/null +++ b/examples/demo10/MEC/match_for_target_app_instance.go @@ -0,0 +1,41 @@ +package mec + +import ( + model "demo10/Model" + "fmt" + "regexp" +) + +// MatchForTargetAppInstance matches application instances by CSE name using regex +// +// This function filters the list of all application instances to find those +// whose names match the provided CSE name pattern using regular expression matching. +// +// @param allAppInstances Array of all available MEC Application Instance IDs +// @param cse_name The CSE name pattern (regex) to match against +// @return []model.AppInstanceID Array of matching application instances +// @return error Returns nil on success, or an error if no matches are found or regex is invalid +func MatchForTargetAppInstance(allAppInstances []model.AppInstanceID, cse_name string) ([]model.AppInstanceID, error) { + // Compile regex for cse_name once + reCSE, err := regexp.Compile(cse_name) + if err != nil { + return nil, fmt.Errorf("invalid regex for cse_name: %v", err) + } + var matchingAppInstances []model.AppInstanceID + for _, appInstance := range allAppInstances { + // If name matches cse_name using compiled regex, store it in our array + if reCSE.MatchString(appInstance.Name) { + matchingAppInstance := model.AppInstanceID{ + ID: appInstance.ID, + Name: appInstance.Name, + NodeName: appInstance.NodeName, + } + matchingAppInstances = append(matchingAppInstances, matchingAppInstance) + } + } + + if len(matchingAppInstances) == 0 { + return nil, fmt.Errorf("MEC App Instance with name %s not found", cse_name) + } + return matchingAppInstances, nil +} diff --git a/examples/demo10/Model/app_instance_id.go b/examples/demo10/Model/app_instance_id.go new file mode 100644 index 0000000000000000000000000000000000000000..7a0444126b071096184f9e87d445b803bc9402f7 --- /dev/null +++ b/examples/demo10/Model/app_instance_id.go @@ -0,0 +1,15 @@ +package model + +// AppInstanceID represents a MEC Application Instance identifier +// +// This structure contains the identification information for a MEC Application Instance, +// including its unique ID, name, and the node name where it is deployed. +// +// Reference: ETSI GS MEC 011 - MEC Application Lifecycle Management API +// +// @struct AppInstanceID +type AppInstanceID struct { + ID string `json:"id"` // Unique identifier for the application instance + Name string `json:"name"` // Name of the application instance + NodeName string `json:"nodeName"` // Name of the MEC host node where the instance is deployed +} \ No newline at end of file diff --git a/examples/demo10/Model/model_adjacent_app_info_notification.go b/examples/demo10/Model/model_adjacent_app_info_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..2e50f57819883f614dba527285d6a1e1500517ad --- /dev/null +++ b/examples/demo10/Model/model_adjacent_app_info_notification.go @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type AdjacentAppInfoNotification struct { + // Shall be set to \"AdjacentAppInfoNotification\". + NotificationType string `json:"notificationType"` + TimeStamp *TimeStamp `json:"timeStamp,omitempty"` + // 1 to N identifiers to associate the information for specific + AssociateId []AssociateId `json:"associateId,omitempty"` + AdjacentAppInfo []AdjacentAppInfoNotificationAdjacentAppInfo `json:"adjacentAppInfo,omitempty"` + Links *Link `json:"_links"` +} diff --git a/examples/demo10/Model/model_adjacent_app_info_notification_adjacent_app_info.go b/examples/demo10/Model/model_adjacent_app_info_notification_adjacent_app_info.go new file mode 100644 index 0000000000000000000000000000000000000000..c3e710bdddf2644cc78baebbcc26626066af6810 --- /dev/null +++ b/examples/demo10/Model/model_adjacent_app_info_notification_adjacent_app_info.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type AdjacentAppInfoNotificationAdjacentAppInfo struct { + // Identifier of the adjacent application instance. + AppInstanceId string `json:"appInstanceId"` + // If present, it represents the communication interface(s) information of the application instance. + CommInterface []CommunicationInterface `json:"commInterface"` +} diff --git a/examples/demo10/Model/model_adjacent_app_info_subscription.go b/examples/demo10/Model/model_adjacent_app_info_subscription.go new file mode 100644 index 0000000000000000000000000000000000000000..9c502c972ae637a825cdab3d11a7758d5cea0c09 --- /dev/null +++ b/examples/demo10/Model/model_adjacent_app_info_subscription.go @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type AdjacentAppInfoSubscription struct { + Links *AdjacentAppInfoSubscriptionLinks `json:"_links,omitempty"` + // URI selected by the service consumer to receive notifications on the subscribed Application Mobility Service. This shall be included both in the request and in response. + CallbackReference string `json:"callbackReference"` + // Shall be set to TRUE by the service consumer to request a test notification via HTTP on the callbackReference URI, specified in ETSI GS MEC 009, as described in clause 6.12a. + RequestTestNotification bool `json:"requestTestNotification,omitempty"` + WebsockNotifConfig *WebsockNotifConfig `json:"websockNotifConfig,omitempty"` + ExpiryDeadline *TimeStamp `json:"expiryDeadline,omitempty"` + FilterCriteria *AdjacentAppInfoSubscriptionFilterCriteria `json:"filterCriteria"` + SubscriptionType *SubscriptionType `json:"subscriptionType"` +} diff --git a/examples/demo10/Model/model_adjacent_app_info_subscription_filter_criteria.go b/examples/demo10/Model/model_adjacent_app_info_subscription_filter_criteria.go new file mode 100644 index 0000000000000000000000000000000000000000..63c6ff0906969180f9722fab38899c600c183e96 --- /dev/null +++ b/examples/demo10/Model/model_adjacent_app_info_subscription_filter_criteria.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// List of filtering criteria for the subscription. Any filtering criteria from below, which is included in the request, shall also be included in the response. +type AdjacentAppInfoSubscriptionFilterCriteria struct { + AppInstanceId string `json:"appInstanceId,omitempty"` +} diff --git a/examples/demo10/Model/model_adjacent_app_info_subscription_links.go b/examples/demo10/Model/model_adjacent_app_info_subscription_links.go new file mode 100644 index 0000000000000000000000000000000000000000..6c0a382969c2a83cb09b8f1d8dd645e1dff96b19 --- /dev/null +++ b/examples/demo10/Model/model_adjacent_app_info_subscription_links.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// Hyperlink related to the resource. This shall be only included in the HTTP responses and in HTTP PUT requests. +type AdjacentAppInfoSubscriptionLinks struct { + Self *LinkType `json:"self"` +} diff --git a/examples/demo10/Model/model_adjacent_app_instance_info.go b/examples/demo10/Model/model_adjacent_app_instance_info.go new file mode 100644 index 0000000000000000000000000000000000000000..13366bad1f5d0d0280a2b7a763a3528ab888e3dc --- /dev/null +++ b/examples/demo10/Model/model_adjacent_app_instance_info.go @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type AdjacentAppInstanceInfo struct { + // Identifier of the application descriptor. + AppDId string `json:"appDId"` + // It specifies the communication interface of application instance. + AppInstanceCommLink []CommunicationInterface `json:"appInstanceCommLink"` + // Identifier of the application instance. + AppInstanceId string `json:"appInstanceId"` + MecHostInformation *MecHostInformation `json:"mecHostInformation,omitempty"` + // dentifier of the application instance that registers to the AMS, which is instantiated from the application descriptor identified by the attribute \"appDId\". + RegisteredInstanceId string `json:"registeredInstanceId,omitempty"` +} diff --git a/examples/demo10/Model/model_app_mobility_service_level.go b/examples/demo10/Model/model_app_mobility_service_level.go new file mode 100644 index 0000000000000000000000000000000000000000..0f3139c55e1b1ab749d5dfbc621953052f7d0960 --- /dev/null +++ b/examples/demo10/Model/model_app_mobility_service_level.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// AppMobilityServiceLevel defines the level of application mobility service +// +// This attribute provides an option for the application instance (server) to communicate +// with the application client before relocating this application instance to another MEC host. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @typedef AppMobilityServiceLevel +type AppMobilityServiceLevel string + +// List of AppMobilityServiceLevel constants +const ( + NOT_ALLOWED_AppMobilityServiceLevel AppMobilityServiceLevel = "APP_MOBILITY_NOT_ALLOWED" // Application mobility is not allowed + WITH_CONFIRMATION_AppMobilityServiceLevel AppMobilityServiceLevel = "APP_MOBILITY_WITH_CONFIRMATION" // Application mobility requires confirmation + WITHOUT_CONFIRMATION_AppMobilityServiceLevel AppMobilityServiceLevel = "APP_MOBILITY_WITHOUT_CONFIRMATION" // Application mobility without confirmation +) diff --git a/examples/demo10/Model/model_app_termination_notification.go b/examples/demo10/Model/model_app_termination_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..c6e436e10646bec60e6ee07000a2303071f020df --- /dev/null +++ b/examples/demo10/Model/model_app_termination_notification.go @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// This type represents the information that the MEC platform notifies the subscribed application instance about the corresponding application instance termination/stop. +type AppTerminationNotification struct { + // Shall be set to AppTerminationNotification. + NotificationType string `json:"notificationType"` + OperationAction *OperationActionType `json:"operationAction"` + // Maximum timeout value in seconds for graceful termination or graceful stop of an application instance. + MaxGracefulTimeout int32 `json:"maxGracefulTimeout"` + Links *AppTerminationNotificationLinks `json:"_links"` +} diff --git a/examples/demo10/Model/model_app_termination_notification__links.go b/examples/demo10/Model/model_app_termination_notification__links.go new file mode 100644 index 0000000000000000000000000000000000000000..74d1af76fdc39bf9ab6d9ca6fd570c342e160122 --- /dev/null +++ b/examples/demo10/Model/model_app_termination_notification__links.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type AppTerminationNotificationLinks struct { + Subscription *LinkType `json:"subscription"` + ConfirmTermination *LinkType `json:"confirmTermination,omitempty"` +} diff --git a/examples/demo10/Model/model_associate_id.go b/examples/demo10/Model/model_associate_id.go new file mode 100644 index 0000000000000000000000000000000000000000..7bc4d6f72fe10a2a4b1af2bf65fb12eda8d4a35b --- /dev/null +++ b/examples/demo10/Model/model_associate_id.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// AssociateId represents an identifier associated with a device or user equipment +// +// This structure is used to identify devices or user equipment (UE) in the context +// of application mobility, such as IP addresses or other device identifiers. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct AssociateId +type AssociateId struct { + Type_ *AssociateIdType `json:"type,omitempty"` // Type of the associate identifier (e.g., UE_IPv4_ADDRESS) + Value string `json:"value,omitempty"` // Value for the identifier +} diff --git a/examples/demo10/Model/model_associate_id_type.go b/examples/demo10/Model/model_associate_id_type.go new file mode 100644 index 0000000000000000000000000000000000000000..d3fdc7be36f387e2db2b54ba66957354922ce858 --- /dev/null +++ b/examples/demo10/Model/model_associate_id_type.go @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// AssociateIdType represents the type of associate identifier +// +// This type defines the possible types of identifiers that can be associated +// with a device or user equipment in the context of application mobility. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @typedef AssociateIdType +type AssociateIdType string + +// List of AssociateIdType constants +const ( + UE_I_PV4_ADDRESS_AssociateIdType AssociateIdType = "UE_IPv4_ADDRESS" // UE IPv4 address identifier + UE_IPV6_ADDRESS_AssociateIdType AssociateIdType = "UE_IPV6_ADDRESS" // UE IPv6 address identifier + NATED_IP_ADDRESS_AssociateIdType AssociateIdType = "NATED_IP_ADDRESS" // NATed IP address identifier + GTP_TEID_AssociateIdType AssociateIdType = "GTP_TEID" // GTP Tunnel Endpoint Identifier +) diff --git a/examples/demo10/Model/model_communication_interface.go b/examples/demo10/Model/model_communication_interface.go new file mode 100644 index 0000000000000000000000000000000000000000..524da89f6cc8001fc98df34acd12aba7e95326b5 --- /dev/null +++ b/examples/demo10/Model/model_communication_interface.go @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type CommunicationInterface struct { + IpAddresses []CommunicationInterfaceIpAddresses `json:"ipAddresses,omitempty"` +} diff --git a/examples/demo10/Model/model_communication_interface_ip_addresses.go b/examples/demo10/Model/model_communication_interface_ip_addresses.go new file mode 100644 index 0000000000000000000000000000000000000000..cdde1a17999152cfc741dc8e374b9f29fc6a04d3 --- /dev/null +++ b/examples/demo10/Model/model_communication_interface_ip_addresses.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type CommunicationInterfaceIpAddresses struct { + Host string `json:"host"` + Port int32 `json:"port"` +} diff --git a/examples/demo10/Model/model_context_transfer_state.go b/examples/demo10/Model/model_context_transfer_state.go new file mode 100644 index 0000000000000000000000000000000000000000..c7ed59cc313a60da8a8b0fe2b7929621c17be1ae --- /dev/null +++ b/examples/demo10/Model/model_context_transfer_state.go @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// ContextTransferState represents the state of user context transfer +// +// This type indicates the state of transferring the user context to another +// application instance during application mobility procedures. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @typedef ContextTransferState +type ContextTransferState string + +// List of ContextTransferState constants +const ( + NOT_TRANSFERRED_ContextTransferState ContextTransferState = "NOT_TRANSFERRED" // User context has not been transferred + USER_CONTEXT_TRANSFER_COMPLETED_ContextTransferState ContextTransferState = "USER_CONTEXT_TRANSFER_COMPLETED" // User context transfer has been completed +) diff --git a/examples/demo10/Model/model_expiry_notification.go b/examples/demo10/Model/model_expiry_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..99d86842e6aa15131f86e58b1eb0604b603fd701 --- /dev/null +++ b/examples/demo10/Model/model_expiry_notification.go @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type ExpiryNotification struct { + // Shall be set to \"ExpiryNotification\". + NotificationType string `json:"notificationType"` + TimeStamp *TimeStamp `json:"timeStamp,omitempty"` + Links *Link `json:"_links"` + ExpiryDeadline *TimeStamp `json:"expiryDeadline"` +} diff --git a/examples/demo10/Model/model_inline_notification.go b/examples/demo10/Model/model_inline_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..8c5b31de0c90a7daee11c80ebf73ab486972064c --- /dev/null +++ b/examples/demo10/Model/model_inline_notification.go @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type InlineNotification struct { + // Not used in client +} diff --git a/examples/demo10/Model/model_inline_subscription.go b/examples/demo10/Model/model_inline_subscription.go new file mode 100644 index 0000000000000000000000000000000000000000..1de3b15ae505c762d5f9872869236004f1f62aff --- /dev/null +++ b/examples/demo10/Model/model_inline_subscription.go @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// InlineSubscription represents an inline subscription for AMS notifications +// +// This structure defines a subscription to Application Mobility Service notifications, +// including the subscription type, callback reference, filter criteria, and optional +// configuration for websocket notifications and expiry deadlines. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct InlineSubscription +type InlineSubscription struct { + /* Discriminator */ + SubscriptionType string `json:"subscriptionType"` // Type of subscription (e.g., "MobilityProcedureSubscription") + + /* Common */ + Links *MobilityProcedureSubscriptionLinks `json:"_links,omitempty"` // Links to related resources + CallbackReference string `json:"callbackReference"` // URL for receiving notifications + RequestTestNotification bool `json:"requestTestNotification,omitempty"` // Whether to request test notification + WebsockNotifConfig *WebsockNotifConfig `json:"websockNotifConfig,omitempty"` // WebSocket notification configuration + ExpiryDeadline *TimeStamp `json:"expiryDeadline,omitempty"` // Subscription expiry deadline + + /* MobilityProcedureSubscription */ + FilterCriteria *MobilityProcedureSubscriptionFilterCriteria `json:"filterCriteria"` // Criteria for filtering notifications + + /* AdjacentAppInfoSubscription */ + // NOTE: to avoid json parameter conflict, use superset filterCriteria from MobilityProcedure + // FilterCriteria *AdjacentAppInfoSubscriptionFilterCriteria `json:"filterCriteria"` +} diff --git a/examples/demo10/Model/model_link.go b/examples/demo10/Model/model_link.go new file mode 100644 index 0000000000000000000000000000000000000000..656146a5cd8284bd94d530ee711d774898d6b73f --- /dev/null +++ b/examples/demo10/Model/model_link.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// Object containing hyperlinks related to the resource. +type Link struct { + Subscription *LinkType `json:"subscription"` +} diff --git a/examples/demo10/Model/model_link_type.go b/examples/demo10/Model/model_link_type.go new file mode 100644 index 0000000000000000000000000000000000000000..daf78404035f8f1b131b8dc3b1b509dbcf8c6cab --- /dev/null +++ b/examples/demo10/Model/model_link_type.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// LinkType represents a type of link to a resource +// +// This data type represents a link to a resource, typically used in REST API +// responses to provide references to related resources (HATEOAS). +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct LinkType +type LinkType struct { + // Href is the URI referring to the resource (e.g., subscription, registration) + Href string `json:"href"` +} diff --git a/examples/demo10/Model/model_mec_host_information.go b/examples/demo10/Model/model_mec_host_information.go new file mode 100644 index 0000000000000000000000000000000000000000..b36a4c43589c7c436ca6d576cf31b408c4686c58 --- /dev/null +++ b/examples/demo10/Model/model_mec_host_information.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type MecHostInformation struct { + // Human-readable name of MEC host. + HostName string `json:"hostName,omitempty"` + HostId *map[string]interface{} `json:"hostId"` +} diff --git a/examples/demo10/Model/model_mobility_procedure_notification.go b/examples/demo10/Model/model_mobility_procedure_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..64f80b013334ff4947f75a0f44731e5975931bb4 --- /dev/null +++ b/examples/demo10/Model/model_mobility_procedure_notification.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// MobilityProcedureNotification represents a mobility procedure notification from AMS +// +// This structure contains the notification sent by the Application Mobility Service +// when a mobility event occurs, such as when an application instance needs to migrate +// to a different MEC host. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct MobilityProcedureNotification +type MobilityProcedureNotification struct { + // NotificationType shall be set to "MobilityProcedureNotification". + NotificationType string `json:"notificationType"` + TimeStamp *TimeStamp `json:"timeStamp,omitempty"` // Timestamp of the notification + // AssociateId contains 1 to N identifiers to associate the information for specific UE(s) and flow(s). + AssociateId []AssociateId `json:"associateId"` + MobilityStatus *MobilityStatus `json:"mobilityStatus"` // Current mobility status + TargetAppInfo *MobilityProcedureNotificationTargetAppInfo `json:"targetAppInfo,omitempty"` // Information about the target application instance + Links *Link `json:"_links"` // Links to related resources +} diff --git a/examples/demo10/Model/model_mobility_procedure_notification_target_app_info.go b/examples/demo10/Model/model_mobility_procedure_notification_target_app_info.go new file mode 100644 index 0000000000000000000000000000000000000000..32497241da8cbb84c5ccabc882ca1bdc297934d2 --- /dev/null +++ b/examples/demo10/Model/model_mobility_procedure_notification_target_app_info.go @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// MobilityProcedureNotificationTargetAppInfo contains information about the target application instance +// +// This structure provides information about the target application instance to which +// the application should migrate during a mobility procedure, including its identifier +// and communication interface details. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct MobilityProcedureNotificationTargetAppInfo +type MobilityProcedureNotificationTargetAppInfo struct { + // AppInstanceId is the identifier of the target application instance + AppInstanceId string `json:"appInstanceId"` + // CommInterface contains communication interface information for the target application instance + CommInterface *CommunicationInterface `json:"commInterface,omitempty"` +} diff --git a/examples/demo10/Model/model_mobility_procedure_subscription.go b/examples/demo10/Model/model_mobility_procedure_subscription.go new file mode 100644 index 0000000000000000000000000000000000000000..c36b0b3a6505555000b5a2c99a63d158b92892cb --- /dev/null +++ b/examples/demo10/Model/model_mobility_procedure_subscription.go @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type MobilityProcedureSubscription struct { + Links *MobilityProcedureSubscriptionLinks `json:"_links,omitempty"` + // URI selected by the service consumer to receive notifications on the subscribed Application Mobility Service. This shall be included both in the request and in response. + CallbackReference string `json:"callbackReference,omitempty"` + // Shall be set to TRUE by the service consumer to request a test notification via HTTP on the callbackReference URI, specified in ETSI GS MEC 009, as described in clause 6.12a. + RequestTestNotification bool `json:"requestTestNotification,omitempty"` + WebsockNotifConfig *WebsockNotifConfig `json:"websockNotifConfig,omitempty"` + ExpiryDeadline *TimeStamp `json:"expiryDeadline,omitempty"` + FilterCriteria *MobilityProcedureSubscriptionFilterCriteria `json:"filterCriteria"` + SubscriptionType *SubscriptionType `json:"subscriptionType"` +} diff --git a/examples/demo10/Model/model_mobility_procedure_subscription_filter_criteria.go b/examples/demo10/Model/model_mobility_procedure_subscription_filter_criteria.go new file mode 100644 index 0000000000000000000000000000000000000000..58189df303ee0023c07a385fda1dd55157dbbe40 --- /dev/null +++ b/examples/demo10/Model/model_mobility_procedure_subscription_filter_criteria.go @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// MobilityProcedureSubscriptionFilterCriteria defines filtering criteria for mobility procedure subscriptions +// +// This structure specifies the criteria used to filter mobility procedure notifications. +// Any filtering criteria included in the request shall also be included in the response. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct MobilityProcedureSubscriptionFilterCriteria +type MobilityProcedureSubscriptionFilterCriteria struct { + // AppInstanceId is the identifier of the application instance that registers the Application Mobility Service. + AppInstanceId string `json:"appInstanceId,omitempty"` + // AssociateId contains 0 to N identifiers to associate the information for specific UE(s) and flow(s). + AssociateId []AssociateId `json:"associateId,omitempty"` + // MobilityStatus specifies the mobility status values to filter notifications. + // If not included in the subscription request, the default value INTER_HOST_MOBILITY_TRIGGERED + // shall be used and included in the response. + MobilityStatus []MobilityStatus `json:"mobilityStatus,omitempty"` +} diff --git a/examples/demo10/Model/model_mobility_procedure_subscription_links.go b/examples/demo10/Model/model_mobility_procedure_subscription_links.go new file mode 100644 index 0000000000000000000000000000000000000000..937f5450c3d67eff2f6a3d410cec7a6c9ac2296b --- /dev/null +++ b/examples/demo10/Model/model_mobility_procedure_subscription_links.go @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type MobilityProcedureSubscriptionLinks struct { + Self *LinkType `json:"self"` +} diff --git a/examples/demo10/Model/model_mobility_status.go b/examples/demo10/Model/model_mobility_status.go new file mode 100644 index 0000000000000000000000000000000000000000..f264d0b086e39504a57635cfbafd12eda29b6bd0 --- /dev/null +++ b/examples/demo10/Model/model_mobility_status.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// MobilityStatus indicates the status of the UE mobility procedure +// +// This type represents the current state of an inter-host mobility procedure, +// indicating whether the mobility has been triggered, completed, or failed. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @typedef MobilityStatus +type MobilityStatus string + +// List of MobilityStatus constants +const ( + TRIGGERED_MobilityStatus MobilityStatus = "INTERHOST_MOVEOUT_TRIGGERED" // Mobility procedure has been triggered + COMPLETED_MobilityStatus MobilityStatus = "INTERHOST_MOVEOUT_COMPLETED" // Mobility procedure has been completed + FAILED_MobilityStatus MobilityStatus = "INTERHOST_MOVEOUT_FAILED" // Mobility procedure has failed +) diff --git a/examples/demo10/Model/model_one_of_inline_notification.go b/examples/demo10/Model/model_one_of_inline_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..49ae7e4f9cc210a647dcc4647863e803ee85ead8 --- /dev/null +++ b/examples/demo10/Model/model_one_of_inline_notification.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type OneOfInlineNotification struct { + /* Discriminator */ + NotificationType string `json:"notificationType"` +} diff --git a/examples/demo10/Model/model_one_of_inline_subscription.go b/examples/demo10/Model/model_one_of_inline_subscription.go new file mode 100644 index 0000000000000000000000000000000000000000..2c68e86a9a7eb55c275cab2d5e9e92e9d56c2dda --- /dev/null +++ b/examples/demo10/Model/model_one_of_inline_subscription.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type OneOfInlineSubscription struct { + /* Discriminator */ + SubscriptionType string `json:"subscriptionType"` +} diff --git a/examples/demo10/Model/model_operation_action_type.go b/examples/demo10/Model/model_operation_action_type.go new file mode 100644 index 0000000000000000000000000000000000000000..d934d5fc4cabb4f53114d5274398d7aabceca97f --- /dev/null +++ b/examples/demo10/Model/model_operation_action_type.go @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// OperationActionType : Operation that is being performed on the MEC application instance. +type OperationActionType string + +// List of OperationActionType +const ( + STOPPING_OperationActionType OperationActionType = "STOPPING" + TERMINATING_OperationActionType OperationActionType = "TERMINATING" +) diff --git a/examples/demo10/Model/model_problem_details.go b/examples/demo10/Model/model_problem_details.go new file mode 100644 index 0000000000000000000000000000000000000000..7d55968705472d6f956e0ff53f83569550632356 --- /dev/null +++ b/examples/demo10/Model/model_problem_details.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type ProblemDetails struct { + // A human-readable explanation specific to this occurrence of the problem + Detail string `json:"detail,omitempty"` + // A URI reference that identifies the specific occurrence of the problem + Instance string `json:"instance,omitempty"` + // The HTTP status code for this occurrence of the problem + Status int32 `json:"status,omitempty"` + // A short, human-readable summary of the problem type + Title string `json:"title,omitempty"` + // A URI reference according to IETF RFC 3986 that identifies the problem type + Type_ string `json:"type,omitempty"` +} diff --git a/examples/demo10/Model/model_registration_info.go b/examples/demo10/Model/model_registration_info.go new file mode 100644 index 0000000000000000000000000000000000000000..2beceb1efb0a66d00e0890a885f20dae4ac9dfda --- /dev/null +++ b/examples/demo10/Model/model_registration_info.go @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// RegistrationInfo represents the registration information for Application Mobility Service +// +// This structure contains the registration details for an application instance with the +// Application Mobility Service (AMS), including device information and service consumer ID. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct RegistrationInfo +type RegistrationInfo struct { + // AppMobilityServiceId is the identifier of registered application mobility service. + // Shall be absent in POST requests, and present otherwise. + AppMobilityServiceId string `json:"appMobilityServiceId,omitempty"` + // DeviceInformation specifies the device served by the application instance which is + // registering the Application Mobility Service. + DeviceInformation []RegistrationInfoDeviceInformation `json:"deviceInformation,omitempty"` + // ExpiryTime indicates the time of Application Mobility Service expiration from the time + // of registration accepted. The value "0" means infinite time, i.e. no expiration. + // The unit of expiry time is one second. + ExpiryTime int32 `json:"expiryTime,omitempty"` + ServiceConsumerId *RegistrationInfoServiceConsumerId `json:"serviceConsumerId"` // Service consumer identifier +} diff --git a/examples/demo10/Model/model_registration_info_device_information.go b/examples/demo10/Model/model_registration_info_device_information.go new file mode 100644 index 0000000000000000000000000000000000000000..5bb56fd56b5761382505190c033e591031c99f87 --- /dev/null +++ b/examples/demo10/Model/model_registration_info_device_information.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// RegistrationInfoDeviceInformation contains device information for AMS registration +// +// This structure specifies the device served by the application instance during +// Application Mobility Service registration, including associate ID, mobility +// service level, and context transfer state. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct RegistrationInfoDeviceInformation +type RegistrationInfoDeviceInformation struct { + AssociateId *AssociateId `json:"associateId"` // Identifier associated with the device + AppMobilityServiceLevel *AppMobilityServiceLevel `json:"appMobilityServiceLevel,omitempty"` // Level of mobility service required + ContextTransferState *ContextTransferState `json:"contextTransferState,omitempty"` // Current state of context transfer +} diff --git a/examples/demo10/Model/model_registration_info_service_consumer_id.go b/examples/demo10/Model/model_registration_info_service_consumer_id.go new file mode 100644 index 0000000000000000000000000000000000000000..e8b7211bfc76ccba7344774a9420173658af3e3f --- /dev/null +++ b/examples/demo10/Model/model_registration_info_service_consumer_id.go @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// RegistrationInfoServiceConsumerId identifies the service consumer requesting AMS +// +// This structure represents the identifier of the service consumer requesting the +// application mobility service, which can be either an application instance ID +// or a MEC platform ID. +// +// Reference: ETSI GS MEC 021 - Application Mobility Service API +// +// @struct RegistrationInfoServiceConsumerId +type RegistrationInfoServiceConsumerId struct { + // AppInstanceId, if present, represents the identifier of the application instance + // registering the Application Mobility Service. + AppInstanceId string `json:"appInstanceId,omitempty"` + // MepId, if present, represents the identifier of the MEC platform registering + // the Application Mobility Service. + MepId string `json:"mepId,omitempty"` +} diff --git a/examples/demo10/Model/model_subscription_link_list.go b/examples/demo10/Model/model_subscription_link_list.go new file mode 100644 index 0000000000000000000000000000000000000000..ae7493d3729867786ecc29e3433eebf379cfe2c0 --- /dev/null +++ b/examples/demo10/Model/model_subscription_link_list.go @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type SubscriptionLinkList struct { + Links *SubscriptionLinkListLinks `json:"_links"` +} diff --git a/examples/demo10/Model/model_subscription_link_list_links.go b/examples/demo10/Model/model_subscription_link_list_links.go new file mode 100644 index 0000000000000000000000000000000000000000..76b7813e259a2faf95a6cfdbc7544006ed223980 --- /dev/null +++ b/examples/demo10/Model/model_subscription_link_list_links.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// List of hyperlinks related to the resource. +type SubscriptionLinkListLinks struct { + Self *LinkType `json:"self"` + // The service consumer’s subscriptions. + Subscription []SubscriptionLinkListSubscription `json:"subscription,omitempty"` +} diff --git a/examples/demo10/Model/model_subscription_link_list_subscription.go b/examples/demo10/Model/model_subscription_link_list_subscription.go new file mode 100644 index 0000000000000000000000000000000000000000..e23d56e8e37e81c47b4a0a93a79a1f28bf90f421 --- /dev/null +++ b/examples/demo10/Model/model_subscription_link_list_subscription.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type SubscriptionLinkListSubscription struct { + // The URI referring to the subscription. + Href string `json:"href"` + SubscriptionType *SubscriptionType `json:"subscriptionType"` +} diff --git a/examples/demo10/Model/model_subscription_type.go b/examples/demo10/Model/model_subscription_type.go new file mode 100644 index 0000000000000000000000000000000000000000..ca4da6f8c0902604ba517b31be7b4f27ae7f697f --- /dev/null +++ b/examples/demo10/Model/model_subscription_type.go @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type SubscriptionType string + +// List of SubscriptionType +const ( + MOBILITY_PROCEDURE_SUBSCRIPTION_SubscriptionType SubscriptionType = "MobilityProcedureSubscription" + ADJACENT_APP_INFO_SUBSCRIPTION_SubscriptionType SubscriptionType = "AdjacentAppInfoSubscription" +) diff --git a/examples/demo10/Model/model_test_notification.go b/examples/demo10/Model/model_test_notification.go new file mode 100644 index 0000000000000000000000000000000000000000..c2cafc46dd63ba1f8788f6831a20c3b93b24d364 --- /dev/null +++ b/examples/demo10/Model/model_test_notification.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type TestNotification struct { + // Shall be set to \"TestNotification\". + NotificationType string `json:"notificationType"` + Links *TestNotificationLinks `json:"_links"` +} diff --git a/examples/demo10/Model/model_test_notification__links.go b/examples/demo10/Model/model_test_notification__links.go new file mode 100644 index 0000000000000000000000000000000000000000..e77f868bb9b534495ab29e0bcaf7d456945263b0 --- /dev/null +++ b/examples/demo10/Model/model_test_notification__links.go @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// Hyperlink related to the resource. +type TestNotificationLinks struct { + Subscription *LinkType `json:"subscription"` +} diff --git a/examples/demo10/Model/model_time_stamp.go b/examples/demo10/Model/model_time_stamp.go new file mode 100644 index 0000000000000000000000000000000000000000..84e953c5c761f20245aac0e2fb12ddf9f1930cf8 --- /dev/null +++ b/examples/demo10/Model/model_time_stamp.go @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +// 'This data type represents the time stamp as Unix-time since January 1, 1970, 00:00:00 UTC' +type TimeStamp struct { + // 'The seconds part of the Time. Time is defined as Unix-time since January 1, 1970, 00:00:00 UTC.' + Seconds int32 `json:"seconds"` + // 'The nanoseconds part of the Time. Time is defined as Unix-time since January 1, 1970, 00:00:00 UTC.' + NanoSeconds int32 `json:"nanoSeconds"` +} diff --git a/examples/demo10/Model/model_websock_notif_config.go b/examples/demo10/Model/model_websock_notif_config.go new file mode 100644 index 0000000000000000000000000000000000000000..8776daae6834738994da9460a08db1711a3812cf --- /dev/null +++ b/examples/demo10/Model/model_websock_notif_config.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

**Micro-service**
[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

**Type & Usage**
Edge Service used by edge applications that want to get information about application mobility in the network

**Note**
AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package model + +type WebsockNotifConfig struct { + // Set by AMS to indicate to the service consumer the Websocket URI to be used for delivering notifications. + WebsocketUri string `json:"websocketUri,omitempty"` + // Set to true by the service consumer to indicate that Websocket delivery is requested. + RequestWebsocketUri bool `json:"requestWebsocketUri,omitempty"` +} diff --git a/examples/demo10/Model/platform_info.go b/examples/demo10/Model/platform_info.go new file mode 100644 index 0000000000000000000000000000000000000000..04240f404d254db08ff26da32e50c696eb459710 --- /dev/null +++ b/examples/demo10/Model/platform_info.go @@ -0,0 +1,15 @@ +package model + +// PlatformInfo contains platform-related information for a MEC application instance +// +// This structure stores the association between an application instance and its +// corresponding AMS service registration and subscription identifiers, along with +// the node name where the instance is deployed. +// +// @struct PlatformInfo +type PlatformInfo struct { + AmsServiceID string `json:"ams_service_id,omitempty"` // AMS service registration identifier + AppInstanceID string `json:"app_instance_id,omitempty"` // MEC Application Instance ID + AmsSubscriptionID string `json:"ams_subscription_id,omitempty"` // AMS subscription identifier + NodeName string `json:"node_name,omitempty"` // Name of the MEC host node +} diff --git a/examples/demo10/build-demo10.sh b/examples/demo10/build-demo10.sh new file mode 100755 index 0000000000000000000000000000000000000000..19a6e08216a2b7c207d4d3e0ce74af3baf87a21c --- /dev/null +++ b/examples/demo10/build-demo10.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e +set -x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin/demo10 + +echo "" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo ">>> Building Demo10 Go MEC APP" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo "" + +go mod tidy +go build -buildvcs=false -o $DEMOBIN . + +echo "" +echo ">>> Demo10 Go MEC APP build completed" diff --git a/examples/demo10/docker_build.sh b/examples/demo10/docker_build.sh new file mode 100755 index 0000000000000000000000000000000000000000..aff96f78f4d391a8cd07fd209714ccaf1a7f6692 --- /dev/null +++ b/examples/demo10/docker_build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e +set -x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin/demo10 + +docker pull golang +docker run --rm -it -v$PWD:/opt/local/etsi/demo10 golang bash -c "cd /opt/local/etsi/demo10 && ./build-demo10.sh && chown -R $UID:$UID ./bin" + +echo "" +echo ">>> Demo Service build completed" diff --git a/examples/demo10/docker_run.sh b/examples/demo10/docker_run.sh new file mode 100755 index 0000000000000000000000000000000000000000..8cfaf9d24694c86c75ade714e7e1df45524dcdee --- /dev/null +++ b/examples/demo10/docker_run.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e +set +x + +docker pull golang +#docker run --rm --expose 80 --expose 443 --publish 80:80 --publish 443:443 -it -v$PWD:/opt/local/etsi/demo10 -v$HOME/var:/opt/local/etsi/var golang bash -c "cd /opt/local/etsi/demo10/bin && ./demo10 ../app_instance.yaml" +docker run --rm -it --publish 9876:9876 -v$PWD:/opt/local/etsi/demo10 -v$HOME/var:/opt/local/etsi/var golang bash -c "cd /opt/local/etsi/demo10/bin && ./demo10" + +echo "" +echo ">>> Done" diff --git a/examples/demo10/dockerize.sh b/examples/demo10/dockerize.sh new file mode 100755 index 0000000000000000000000000000000000000000..5b83fdc7c2c95116828fa5631f05a4c6bd75bd5b --- /dev/null +++ b/examples/demo10/dockerize.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e +set +x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin + +echo "" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo ">>> Dockerizing Demo10 Server" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo "" + +# Copy Dockerfile & config to bin folder +cp $BASEDIR/Dockerfile $DEMOBIN +# cp $BASEDIR/src/backend/app_instance.yaml $DEMOBIN +cp $BASEDIR/entrypoint.sh $DEMOBIN + +echo ">>> Dockerizing" +cd $DEMOBIN +docker build --no-cache --rm -t meep-docker-registry:30001/demo10 . +docker push meep-docker-registry:30001/demo10 +cd $BASEDIR + +echo "" +echo ">>> Done" diff --git a/examples/demo10/entrypoint.sh b/examples/demo10/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..23101fb754547ca43e70f52c136f1f867724cec0 --- /dev/null +++ b/examples/demo10/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +echo "mode: advantedge" >app_instance.yaml +echo "sandbox:" >>app_instance.yaml +echo "mecplatform: ${MEEP_MEP_NAME}" >>app_instance.yaml +echo "appid:" ${MEEP_APP_ID} >>app_instance.yaml +echo "localurl: ${MEEP_POD_NAME}" >>app_instance.yaml +echo "port:" >>app_instance.yaml + +# Start service +exec /demo10 ./app_instance.yaml diff --git a/examples/demo10/go.mod b/examples/demo10/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..20af1ed43eb67e65f81ec05f0962b66e08d00209 --- /dev/null +++ b/examples/demo10/go.mod @@ -0,0 +1,5 @@ +module demo10 + +go 1.17 + +require github.com/google/uuid v1.6.0 diff --git a/examples/demo10/go.sum b/examples/demo10/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..40e65d629b6dea1b3ca51f5778cd7ca9d29cff27 --- /dev/null +++ b/examples/demo10/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= \ No newline at end of file diff --git a/examples/demo10/main.go b/examples/demo10/main.go new file mode 100644 index 0000000000000000000000000000000000000000..e349699051b86b21915a3e633023a09e2e5052d1 --- /dev/null +++ b/examples/demo10/main.go @@ -0,0 +1,292 @@ +package main + +import ( + mec "demo10/MEC" + model "demo10/Model" + onem2m "demo10/oneM2M" + "fmt" + "log" + "math/rand" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +// MN-CSE platform details +var platformURL = "" // IoT platform URL obtained from MEC platform +var sandboxURL = "https://mec-platform.etsi.org" // MEC platform base URL + +var cse_id = "" // CSE Structured ID +var cse_name = "mep-cse-mn" // CSE Name (for some API calls) +var AE_ID = "CSmartCar" // Application Entity ID +var App_Name = "SmartCar" // Application Resource Name +var App_Id = "N-Smart_Car_Application" +var appVersion = "1.0.0" +var containerName = "SmartCarContainer" +var remote_cse_id = "/laboai-id-in" + +// MEC platform details +var sandbox_name = "sbxykqjr17" // Namespace allocated to the user upon login +var DeviceId = "10.100.0.1" // This is the IP of UE moving on map in Sandbox +var ams_pltf = "mep1" + +// Endpoints for recieving notifications from MEC platform +var server_port = "9876" +var ams_notification_endpoint = "/ams/notify" +var server_ip = "0.0.0.0" +var ams_callback_address = "172.29.10.52" + +var notificationChan = make(chan string, 10) // Channel for receiving AMS notifications +var currentAppInstanceId = "" // Current MEC Application Instance ID +var platformDetails = []map[string]model.PlatformInfo{} // Platform information for each app instance +var associateId = &model.AssociateId{ + Type_: &[]model.AssociateIdType{"UE_IPv4_ADDRESS"}[0], + Value: DeviceId, +} +var deviceInfo = &model.RegistrationInfoDeviceInformation{ + AssociateId: associateId, + AppMobilityServiceLevel: &[]model.AppMobilityServiceLevel{"APP_MOBILITY_WITHOUT_CONFIRMATION"}[0], + ContextTransferState: &[]model.ContextTransferState{"NOT_TRANSFERRED"}[0], +} + +// initApp initializes the application and connects to the oneM2M platform +// +// This function performs the following operations: +// - Starts an HTTP server to listen for AMS notifications +// - Retrieves MEC Application Instance IDs from the MEC platform +// - Matches target app instances based on CSE name +// - Creates AMS registrations and subscriptions for each matching app instance +// - Fetches IoT platform information from MEC +// - Initializes the oneM2M application on the IoT platform +// +// Returns an error if any initialization step fails. +// +// @return error Returns nil on success, or an error describing the failure +func initApp() error { + // Start HTTP server to listen for sensor connections + log.Println("Starting HTTP server on port 9876 to listen for sensor connections...") + server := &http.Server{Addr: fmt.Sprintf("%s:%s", server_ip, server_port)} + // Set up HTTP routes + http.HandleFunc(ams_notification_endpoint, func(w http.ResponseWriter, r *http.Request) { + id, err := mec.AMSNotificationHandler(w, r) + if err != nil { + log.Printf("Error handling AMS notification: %v", err) + } else if id != "" { + notificationChan <- id + } + }) + + // Start server in a goroutine so it doesn't block + go func() { + log.Println("Server goroutine starting...") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Printf("Server error: %v", err) + log.Printf("ERROR: Could not start server: %v", err) + } + }() + + // Give the server a moment to start and check if it's running + time.Sleep(1 * time.Second) + + // log.Println("✓ HTTP server started and listening on 0.0.0.0:9876") + + log.Printf("Initializing %s (version %s)", App_Id, appVersion) + log.Println("Obtaining MEC App Instance ID of mn-cse") + + // Fetch MEC App Instance ID to find the correct App Instance for mn-cse + // Construct MEC App Instance URL + url := fmt.Sprintf("%s/%s/sandbox-ctrl/v1/applications", sandboxURL, sandbox_name) + + // Get all MEC App Instance ID + allAppInstances, err := mec.GetAppInstances(url) + if err != nil { + return fmt.Errorf("failed to get MEC App Instance IDs: %v", err) + } + log.Printf("Total MEC App Instances retrieved: %d", len(allAppInstances)) + log.Printf("MEC App Instances: %+v", allAppInstances) + + // Array to store matching app instances + matchingAppInstances, err := mec.MatchForTargetAppInstance(allAppInstances, cse_name) + if err != nil { + return fmt.Errorf("failed to match MEC App Instances: %v", err) + } + + // Create AMS registration and subscription for each matching App Instance + callbackURL := fmt.Sprintf("http://%s:%s%s", ams_callback_address /*server_ip*/, server_port, ams_notification_endpoint) // FSCOM FIXME: Use server_ip for local testing due to dockerized golanf execution environment + platformDetails, err = mec.RegistrationAndSubscriptionHandler(matchingAppInstances, sandboxURL, + sandbox_name, callbackURL, ¤tAppInstanceId, deviceInfo, associateId) + if err != nil { + return fmt.Errorf("failed to create AMS registration and subscription: %v", err) + } + + // Request IoT API for IoT platform urls + log.Println("Fetching IoT platform information from MEC...") + + // Current App Instance ID must be set from previous step + var current_pltf_details model.PlatformInfo + for _, pltf := range platformDetails { + if platformInfo, exists := pltf[currentAppInstanceId]; exists { + current_pltf_details = platformInfo + break + } + } + log.Printf("Current platform details: %v", current_pltf_details) + + iot_url := fmt.Sprintf("%s/%s/%s/iots/v1/registered_iot_platforms", sandboxURL, sandbox_name, current_pltf_details.NodeName) + iot_platforms, err := mec.GETIotPlatformInfo(iot_url) + if err != nil { + return fmt.Errorf("failed to get IoT platform info: %v", err) + } + + platformURL = iot_platforms[0]["customServicesTransportInfo"].([]interface{})[0].(map[string]interface{})["endpoint"].(map[string]interface{})["uris"].([]interface{})[0].(string) + log.Printf("IoT platform URL: %s", platformURL) + cse_id = iot_platforms[0]["customServicesTransportInfo"].([]interface{})[0].(map[string]interface{})["id"].(string) + log.Printf("CSE ID: %s", cse_id) + cse_name = iot_platforms[0]["customServicesTransportInfo"].([]interface{})[0].(map[string]interface{})["name"].(string) + log.Printf("CSE Name: %s", cse_name) + + // Initialize oneM2M application + err = onem2m.InitOneM2MApp(platformURL, cse_name, cse_id, App_Name, App_Id, AE_ID, containerName, remote_cse_id) + if err != nil { + return fmt.Errorf("failed to initialize oneM2M application: %v", err) + } + // Create subscription for notification server + // err = onem2m.CreateSubscription(platformURL, cse_name, App_Name, containerName) + // if err != nil { + // return fmt.Errorf("failed to create oneM2M subscription: %v", err) + // } + return nil +} + +// main is the entry point of the application +// +// This function: +// - Sets up graceful shutdown handling for SIGINT and SIGTERM signals +// - Initializes the application by calling initApp() +// - Starts a goroutine to handle AMS mobility notifications +// - Continuously sends telemetry data (temperature, speed, fuel) to the oneM2M platform +// +// The application runs until terminated by a system signal, at which point it: +// - Retrieves the latest data from the oneM2M platform +// - Deregisters the Application Entity from the platform +// - Exits gracefully +func main() { + log.SetFlags(log.LstdFlags | log.Lshortfile) + // Handle graceful shutdown on system kill signals + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigs + latestData, err := onem2m.RetrieveLatestData(platformURL, cse_name, App_Name, AE_ID, containerName) + if err != nil { + log.Printf("Failed to retrieve latest data: %v", err) + } else { + log.Printf("Latest data: %s", latestData) + } + if err := onem2m.DeregisterAE(platformURL, cse_name, cse_id, App_Name, AE_ID); err != nil { + log.Printf("Failed to deregister AE: %v", err) + } + log.Println("\nReceived shutdown signal, exiting...") + os.Exit(0) + }() + + // Initialize the application + if err := initApp(); err != nil { + log.Fatalf("Failed to initialize application: %v", err) + os.Exit(1) + } + + // Start goroutine to handle received target app instance IDs + go func() { + for targetAppInstanceID := range notificationChan { + // Get platform details for the received ID + var targetPltfDetails model.PlatformInfo + for _, pltf := range platformDetails { + if platformInfo, exists := pltf[targetAppInstanceID]; exists { + targetPltfDetails = platformInfo + break + } + } + log.Printf("Target platform details: %v", targetPltfDetails) + + // Connect to new IoT platform + iot_url := fmt.Sprintf("%s/%s/%s/iots/v1/registered_iot_platforms", sandboxURL, sandbox_name, targetPltfDetails.NodeName) + iot_platforms, err := mec.GETIotPlatformInfo(iot_url) + if err != nil { + log.Printf("Failed to get IoT platform info for target app instance %s: %v", targetAppInstanceID, err) + continue + } + // Temporarily store old platform URL + old_platformURL := platformURL + old_cse_id := cse_id + old_cse_name := cse_name + + platformURL = iot_platforms[0]["customServicesTransportInfo"].([]interface{})[0].(map[string]interface{})["endpoint"].(map[string]interface{})["uris"].([]interface{})[0].(string) + log.Printf("New IoT platform URL after mobility: %s", platformURL) + + cse_name = iot_platforms[0]["customServicesTransportInfo"].([]interface{})[0].(map[string]interface{})["name"].(string) + cse_id = iot_platforms[0]["customServicesTransportInfo"].([]interface{})[0].(map[string]interface{})["id"].(string) + + // Re-initialize oneM2M application on the new platform + err = onem2m.InitOneM2MApp(platformURL, cse_name, cse_id, App_Name, App_Id, AE_ID, containerName, remote_cse_id) + if err != nil { + log.Printf("Failed to re-initialize oneM2M application after mobility: %v", err) + continue + } else { + log.Printf("Successfully re-initialized oneM2M application on new platform after mobility") + } + + // Disconnect from previous platform + log.Printf("Disconnecting from previous platform: %s", old_platformURL) + if err := onem2m.DeregisterAE(old_platformURL, old_cse_name, old_cse_id, App_Name, AE_ID); err != nil { + log.Printf("Failed to deregister AE from old platform: %v", err) + } else { + log.Printf("Successfully deregistered AE from old platform") + } + + // Update AMS registration to reflect new platform connection + log.Printf("Updating AMS registration to reflect new platform connection...") + + // PUT request to AMS for currentAppInstanceId to switch + ams_url := fmt.Sprintf("%s/%s/%s/amsi/v1/app_mobility_services", sandboxURL, sandbox_name, ams_pltf) + err = mec.PutAMSRegistrationInfo(ams_url, targetAppInstanceID, platformDetails, ¤tAppInstanceId, associateId) + if err != nil { + log.Printf("Failed to update AMS registration info for target app instance %s: %v", targetAppInstanceID, err) + continue + } else { + log.Printf("Successfully updated AMS registration info for target app instance %s", targetAppInstanceID) + } + + // Update AMS subscription to reflect new platform connection + log.Printf("Updating AMS subscription to reflect new platform connection...") + ams_url = fmt.Sprintf("%s/%s/%s/amsi/v1/subscriptions", sandboxURL, sandbox_name, ams_pltf) + callbackURL := fmt.Sprintf("http://%s:%s%s", ams_callback_address /*server_ip*/, server_port, ams_notification_endpoint) // FSCOM FIXME: Use server_ip for local testing due to dockerized golanf execution environment + err = mec.PUTAMSSubscriptionInfo(ams_url, targetAppInstanceID, platformDetails, ¤tAppInstanceId, callbackURL, associateId) + if err != nil { + log.Printf("Failed to update AMS subscription info for target app instance %s: %v", targetAppInstanceID, err) + continue + } else { + log.Printf("Successfully updated AMS subscription info for target app instance %s", targetAppInstanceID) + } + currentAppInstanceId = targetAppInstanceID + } + }() + + // Example: Send telemetry data + speed := []int{60, 62, 58, 65, 68} + temperature := []float64{25.5, 26.0, 24.8, 25.2, 25.9} + fuel := []int{75, 74, 73, 72, 71} + // For only terminates on system kill signal + for { + telemetryData := fmt.Sprintf(`{"temperature": %f, "speed": %d, "fuel": %d}`, + temperature[rand.Intn(len(temperature))], + speed[rand.Intn(len(speed))], + fuel[rand.Intn(len(fuel))]) + if err := onem2m.CreateContentInstance(platformURL, cse_name, App_Name, AE_ID, containerName, remote_cse_id, telemetryData); err != nil { + log.Printf("Failed to send telemetry: %v", err) + } + time.Sleep(5 * time.Second) + } +} diff --git a/examples/demo10/oneM2M/createAE.go b/examples/demo10/oneM2M/createAE.go new file mode 100644 index 0000000000000000000000000000000000000000..aeff4a0f9fa6718223a7036ff3d1804867092d32 --- /dev/null +++ b/examples/demo10/oneM2M/createAE.go @@ -0,0 +1,60 @@ +package onem2m + +import ( + "demo10/utils" + "fmt" + "io" + "log" + "net/http" +) + +// CreateAE creates an Application Entity (AE) on the oneM2M platform +// +// This function creates an AE resource on the oneM2M CSE. The AE represents +// the application in the oneM2M system and provides the entry point for +// application resources and data. +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cse_name The name of the Common Services Entity (CSE) +// @param cse_id The structured ID of the CSE +// @param App_Name The resource name for the application +// @param App_Id The application ID (API identifier) +// @param AE_ID The Application Entity ID for authentication +// @param remote_cse_id The remote CSE ID for access control +// @return error Returns nil on success, or an error if AE creation fails +func CreateAE(platformURL, cse_name, cse_id, App_Name, App_Id, AE_ID, remote_cse_id string) error { + log.Println("Creating Application Entity (AE)...") + + reqBody := fmt.Sprintf(`{ + "m2m:ae": { + "rn": "%s", + "api": "%s", + "rr": true, + "lbl": ["Type/SmartCar", "Category/Vehicle"], + "srv": ["5"], + "at": ["%s"] + } + }`, App_Name, App_Id, remote_cse_id) + + url := fmt.Sprintf("%s/~%s/%s", platformURL, cse_id, cse_name) + log.Println("url (Create AE):", url) + resp, err := utils.MakeOneM2MRequest("POST", url, reqBody, 2, AE_ID) + if err != nil { + return err + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode == http.StatusCreated { + log.Println("✓ Application Entity (AE) created successfully") + return nil + } else if resp.StatusCode == http.StatusConflict || resp.StatusCode == http.StatusForbidden { + log.Println("⚠ AE already exists") + return nil + } + + return fmt.Errorf("failed to create AE: status %d, body: %s", resp.StatusCode, string(body)) +} diff --git a/examples/demo10/oneM2M/createContentInstance.go b/examples/demo10/oneM2M/createContentInstance.go new file mode 100644 index 0000000000000000000000000000000000000000..823687dab8ed78ef2e13f05c9285d0317c66454a --- /dev/null +++ b/examples/demo10/oneM2M/createContentInstance.go @@ -0,0 +1,59 @@ +package onem2m + +import ( + "demo10/utils" + "fmt" + "io" + "log" + "net/http" + "strings" +) + +// CreateContentInstance creates a ContentInstance resource in the specified container +// +// This function creates a ContentInstance resource containing the provided data. +// ContentInstances represent actual data values stored in a Container and are +// the leaf nodes in the oneM2M resource tree. +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cse_name The name of the Common Services Entity (CSE) +// @param App_Name The resource name for the application +// @param AE_ID The Application Entity ID for authentication +// @param containerName The name of the container where the content will be stored +// @param remote_cse_id The remote CSE ID for access control +// @param data The JSON data string to store in the ContentInstance +// @return error Returns nil on success, or an error if ContentInstance creation fails +func CreateContentInstance(platformURL, cse_name, App_Name, AE_ID, containerName, remote_cse_id, data string) error { + log.Printf("Creating ContentInstance with data: %s", data) + + // Escape JSON quotes in the content field + escapedData := strings.ReplaceAll(data, `"`, `\"`) + + reqBody := fmt.Sprintf(`{ + "m2m:cin": { + "con": "%s", + "lbl": ["telemetry"], + "at": ["%s"], + "aa": ["lbl", "con"] + } + }`, escapedData, remote_cse_id) + + url := fmt.Sprintf("%s/%s/%s/%s", platformURL, cse_name, App_Name, containerName) + + resp, err := utils.MakeOneM2MRequest("POST", url, reqBody, 4, AE_ID) + if err != nil { + return fmt.Errorf("request error: %v", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode == http.StatusCreated { + log.Println("✓ ContentInstance created successfully") + return nil + } + + return fmt.Errorf("failed to create content instance: status %d, body: %s", resp.StatusCode, string(body)) +} diff --git a/examples/demo10/oneM2M/createSubscription.go b/examples/demo10/oneM2M/createSubscription.go new file mode 100644 index 0000000000000000000000000000000000000000..239ffe8100b1d06736645d4e875c1197f503d615 --- /dev/null +++ b/examples/demo10/oneM2M/createSubscription.go @@ -0,0 +1,76 @@ +package onem2m + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// CreateSubscription creates a Subscription resource on the specified container +// +// This function creates a Subscription resource that enables the oneM2M platform +// to send notifications to a specified endpoint when events occur on the container +// (e.g., when new ContentInstances are created). +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cseName The name of the Common Services Entity (CSE) +// @param appName The resource name for the application +// @param containerName The name of the container to subscribe to +// @return error Returns nil on success, or an error if subscription creation fails +func CreateSubscription(platformURL, cseName, appName, containerName string) error { + subscriptionURL := fmt.Sprintf("%s/%s/%s/%s", platformURL, cseName, appName, containerName) + + target := subscriptionURL + // Subscription resource body + body := map[string]interface{}{ + "m2m:sub": map[string]interface{}{ + "rn": "DataSyncSub", + "nu": []string{"https://192.168.20.163:9999/notifications"}, + "nct": 1, + "enc": map[string]interface{}{ + "net": []int{3}, + }, + }, + } + jsonBody, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("failed to marshal subscription body: %v", err) + } + + adminUser := "CAdmin" + adminSecret := "ikram123" + authString := fmt.Sprintf("%s:%s", adminUser, adminSecret) + authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(authString)) + + req, err := http.NewRequest("POST", target, bytes.NewBuffer(jsonBody)) + if err != nil { + return fmt.Errorf("failed to create subscription request: %v", err) + } + req.Header.Set("X-M2M-Origin", adminUser) + req.Header.Set("X-M2M-RI", "req-admin") + req.Header.Set("Authorization", authHeader) + req.Header.Set("X-M2M-RVI", "5") + req.Header.Set("Content-Type", "application/json;ty=23") + + // Skip TLS verification for development/testing + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send subscription request: %v", err) + } + defer resp.Body.Close() + respBody, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode != 201 && resp.StatusCode != 409 { // 409: Already exists + return fmt.Errorf("subscription creation failed: %s", string(respBody)) + } + return nil +} diff --git a/examples/demo10/oneM2M/createcontainer.go b/examples/demo10/oneM2M/createcontainer.go new file mode 100644 index 0000000000000000000000000000000000000000..a9861fe2f11786546f703f676f822eab0db7d0e5 --- /dev/null +++ b/examples/demo10/oneM2M/createcontainer.go @@ -0,0 +1,59 @@ +package onem2m + +import ( + "demo10/utils" + "fmt" + "io" + "log" + "net/http" +) + +// CreateContainer creates a Container resource under the Application Entity +// +// This function creates a Container resource in the oneM2M resource tree. +// Containers are used to store ContentInstance resources (data) and provide +// a logical grouping mechanism for related data. +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cse_name The name of the Common Services Entity (CSE) +// @param cse_id The structured ID of the CSE +// @param App_Name The resource name for the application +// @param AE_ID The Application Entity ID for authentication +// @param containerName The name of the container to create +// @param remote_cse_id The remote CSE ID for access control +// @return error Returns nil on success, or an error if container creation fails +func CreateContainer(platformURL, cse_name, cse_id, App_Name, AE_ID, containerName, remote_cse_id string) error { + log.Println("Creating Container...") + + reqBody := fmt.Sprintf(`{ + "m2m:cnt": { + "rn": "%s", + "mni": 10, + "at": ["%s"], + "lbl": ["Data/Telemetry"], + "aa": ["lbl"] + } + }`, containerName, remote_cse_id) + + url := fmt.Sprintf("%s/~/%s/%s/%s", platformURL, cse_id, cse_name, App_Name) + log.Println("url (Create Container):", url) + resp, err := utils.MakeOneM2MRequest("POST", url, reqBody, 3, AE_ID) + if err != nil { + return fmt.Errorf("request error: %v", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode == http.StatusCreated { + log.Println("✓ Container created successfully") + return nil + } else if resp.StatusCode == http.StatusConflict { + log.Println("⚠ Container already exists") + return nil + } + + return fmt.Errorf("failed to create container: status %d, body: %s", resp.StatusCode, string(body)) +} diff --git a/examples/demo10/oneM2M/deregister.go b/examples/demo10/oneM2M/deregister.go new file mode 100644 index 0000000000000000000000000000000000000000..66d582cdd81a405820719682432c929fd266ce51 --- /dev/null +++ b/examples/demo10/oneM2M/deregister.go @@ -0,0 +1,40 @@ +package onem2m + +import ( + "demo10/utils" + "fmt" + "io" + "log" + "net/http" +) + +// DeregisterAE deregisters the Application Entity from the oneM2M platform +// +// This function removes the AE resource from the oneM2M CSE, effectively +// unregistering the application from the platform. This should be called +// during application shutdown or when migrating to a different platform. +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cseName The name of the Common Services Entity (CSE) +// @param cse_id The structured ID of the CSE +// @param appName The resource name for the application +// @param aeID The Application Entity ID for authentication +// @return error Returns nil on success, or an error if deregistration fails +func DeregisterAE(platformURL, cseName, cse_id, appName, aeID string) error { + aeResource := fmt.Sprintf("%s/~%s/%s/%s", platformURL, cse_id, cseName, appName) + resp, err := utils.MakeOneM2MRequest("DELETE", aeResource, "", 2, aeID) + if err != nil { + return fmt.Errorf("failed to deregister AE: %v", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("failed to deregister AE, status: %s, response: %s", resp.Status, string(body)) + } + + log.Printf("✓ AE deregistered successfully, response: %s", string(body)) + return nil +} diff --git a/examples/demo10/oneM2M/oneM2m_init.go b/examples/demo10/oneM2M/oneM2m_init.go new file mode 100644 index 0000000000000000000000000000000000000000..8f5402b1eddd084fc0caa78d6b2be169305021ad --- /dev/null +++ b/examples/demo10/oneM2M/oneM2m_init.go @@ -0,0 +1,49 @@ +package onem2m + +import ( + "log" + "time" +) + +// InitOneM2MApp initializes a oneM2M application by creating an AE and container +// +// This function performs the complete initialization sequence for a oneM2M application: +// 1. Creates an Application Entity (AE) on the oneM2M platform +// 2. Waits briefly to ensure the AE is registered +// 3. Creates a Container under the AE for storing content instances +// 4. Verifies that the container was created successfully +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cse_name The name of the Common Services Entity (CSE) +// @param cse_id The structured ID of the CSE +// @param App_Name The resource name for the application +// @param App_Id The application ID +// @param AE_ID The Application Entity ID +// @param containerName The name of the container to create +// @param remote_cse_id The remote CSE ID for access control +// @return error Returns nil on success, or an error if initialization fails +func InitOneM2MApp(platformURL, cse_name, cse_id, App_Name, + App_Id, AE_ID, containerName, remote_cse_id string) error { + log.Printf("Connecting to MN-CSE: %s", platformURL) + // Create AE + if err := CreateAE(platformURL, cse_name, cse_id, App_Name, App_Id, AE_ID, remote_cse_id); err != nil { + return err + } + // Wait briefly to ensure AE is registered + time.Sleep(2 * time.Second) + + // Create Container + if err := CreateContainer(platformURL, cse_name, cse_id, App_Name, AE_ID, containerName, remote_cse_id); err != nil { + return err + } + + // Verify container + if err := RetrieveContainer(platformURL, cse_name, cse_id, App_Name, AE_ID, containerName); err != nil { + log.Printf("Warning: Could not verify container: %v", err) + } + + log.Println("✓ Application initialized successfully") + return nil +} diff --git a/examples/demo10/oneM2M/retrieveContainer.go b/examples/demo10/oneM2M/retrieveContainer.go new file mode 100644 index 0000000000000000000000000000000000000000..8f7299446322a5003f226e95add30d61534f8750 --- /dev/null +++ b/examples/demo10/oneM2M/retrieveContainer.go @@ -0,0 +1,42 @@ +package onem2m + +import ( + "demo10/utils" + "fmt" + "io" + "log" + "net/http" +) + +// RetrieveContainer retrieves container details from the oneM2M platform +// +// This function queries the oneM2M platform to retrieve information about +// a specific Container resource, verifying its existence and accessibility. +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cse_name The name of the Common Services Entity (CSE) +// @param cse_id The structured ID of the CSE +// @param App_Name The resource name for the application +// @param AE_ID The Application Entity ID for authentication +// @param containerName The name of the container to retrieve +// @return error Returns nil on success, or an error if retrieval fails +func RetrieveContainer(platformURL, cse_name, cse_id, App_Name, AE_ID, containerName string) error { + url := fmt.Sprintf("%s/~%s/%s/%s/%s", platformURL, cse_id, cse_name, App_Name, containerName) + + resp, err := utils.MakeOneM2MRequest("GET", url, "", 0, AE_ID) + if err != nil { + return fmt.Errorf("error retrieving container: %v", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode == http.StatusOK { + log.Println("✓ Container exists and is accessible") + return nil + } + + return fmt.Errorf("container not found: status %d, body: %s", resp.StatusCode, string(body)) +} diff --git a/examples/demo10/oneM2M/retrieveLatestData.go b/examples/demo10/oneM2M/retrieveLatestData.go new file mode 100644 index 0000000000000000000000000000000000000000..c558a3c8f1392a7440842ee41d829a82f3b54e01 --- /dev/null +++ b/examples/demo10/oneM2M/retrieveLatestData.go @@ -0,0 +1,45 @@ +package onem2m + +import ( + "demo10/utils" + "fmt" + "io" + "log" + "net/http" +) + +// RetrieveLatestData retrieves the latest ContentInstance from the specified container +// +// This function queries the oneM2M platform to retrieve the most recent +// ContentInstance stored in the container using the 'la' (latest) virtual +// resource identifier. +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param platformURL The base URL of the oneM2M CSE platform +// @param cse_name The name of the Common Services Entity (CSE) +// @param App_Name The resource name for the application +// @param AE_ID The Application Entity ID for authentication +// @param containerName The name of the container to query +// @return string The JSON content of the latest ContentInstance +// @return error Returns nil on success, or an error if retrieval fails +func RetrieveLatestData(platformURL, cse_name, App_Name, AE_ID, containerName string) (string, error) { + // Use 'la' (latest) to get the most recent ContentInstance + // ToDo: Fix variable name cse_id to cse_name // check this file + url := fmt.Sprintf("%s/%s/%s/%s/la", platformURL, cse_name, App_Name, containerName) + + resp, err := utils.MakeOneM2MRequest("GET", url, "", 0, AE_ID) + if err != nil { + return "", fmt.Errorf("error retrieving data: %v", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode == http.StatusOK { + log.Println("✓ Latest data retrieved successfully") + return string(body), nil + } + + return "", fmt.Errorf("failed to retrieve data: status %d", resp.StatusCode) +} diff --git a/examples/demo10/utils/mec_req.go b/examples/demo10/utils/mec_req.go new file mode 100644 index 0000000000000000000000000000000000000000..c24ae87aeb053b6515faf2fd22cc9a590011e416 --- /dev/null +++ b/examples/demo10/utils/mec_req.go @@ -0,0 +1,61 @@ +package utils + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "log" + "net/http" + "time" +) + +// SendMECRequest sends an HTTP request to a MEC platform endpoint +// +// This function creates and sends an HTTP request to a MEC platform API endpoint. +// It handles JSON payload marshaling for POST/PUT requests and sets appropriate +// headers for MEC API communication. TLS verification is disabled for development/testing. +// +// @param method HTTP method (GET, POST, PUT, DELETE, etc.) +// @param url The complete URL endpoint for the MEC API request +// @param payload The payload to send (will be marshaled to JSON for non-GET requests) +// @return *http.Response The HTTP response from the MEC platform +// @return error Returns nil on success, or an error if the request fails +func SendMECRequest(method, url string, payload interface{}) (*http.Response, error) { + client := &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + + var req *http.Request + var err error + + // Handle GET requests with no payload + if method == "GET" && payload == nil { + req, err = http.NewRequestWithContext(context.Background(), method, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %v", err) + } + } else { + // Handle requests with payload + reqBody, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("failed to marshal payload: %v", err) + } + log.Println("Request body:", string(reqBody)) + + req, err = http.NewRequestWithContext(context.Background(), method, url, bytes.NewBuffer(reqBody)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "Go-http-client/1.1") + + return client.Do(req) +} diff --git a/examples/demo10/utils/onem2m_req.go b/examples/demo10/utils/onem2m_req.go new file mode 100644 index 0000000000000000000000000000000000000000..7fb7526b23923f29e24df4a80c458ddd4e8c09e2 --- /dev/null +++ b/examples/demo10/utils/onem2m_req.go @@ -0,0 +1,67 @@ +package utils + +import ( + "crypto/tls" + "encoding/base64" + "fmt" + "net/http" + "strings" + "time" + + "github.com/google/uuid" +) + +// MakeOneM2MRequest creates and sends a oneM2M HTTP request +// +// This function creates and sends an HTTP request to a oneM2M CSE platform endpoint. +// It sets the required oneM2M headers including X-M2M-Origin, X-M2M-RI (request ID), +// X-M2M-RVI (release version indicator), and Content-Type with resource type (ty). +// +// Reference: oneM2M TS-0001 - Functional Architecture +// +// @param method HTTP method (GET, POST, PUT, DELETE, etc.) +// @param url The complete URL endpoint for the oneM2M API request +// @param body The request body as a string (JSON formatted) +// @param ty Resource type code for oneM2M resources (e.g., 2 for AE, 3 for Container) +// @param originator The originator identifier (typically the AE-ID) +// @return *http.Response The HTTP response from the oneM2M platform +// @return error Returns nil on success, or an error if the request fails +func MakeOneM2MRequest(method, url, body string, ty int, originator string) (*http.Response, error) { + client := &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + Timeout: 10 * time.Second} + reqID := uuid.New().String() + + // if originator == "" { + // originator = AE_ID + // } + + headers := map[string]string{ + "X-M2M-Origin": originator, + "X-M2M-RI": reqID, + "X-M2M-RVI": "5", + "Accept": "application/json", + "Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("CAdmin:ikram123")), + } + + if method == "POST" { + headers["Content-Type"] = fmt.Sprintf("application/json;ty=%d", ty) + } + + req, err := http.NewRequest(method, url, strings.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %v", err) + } + + for key, value := range headers { + req.Header.Set(key, value) + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + + return resp, nil +} diff --git a/examples/demo3/src/backend/main.go b/examples/demo3/src/backend/main.go index 945729a157dadc08d37de57451df482c5c734860..bcc7d2f617ac1a444cd549f8356bf2bedf522212 100644 --- a/examples/demo3/src/backend/main.go +++ b/examples/demo3/src/backend/main.go @@ -24,6 +24,7 @@ package main import ( + "crypto/tls" "net/http" "os" "os/signal" @@ -36,6 +37,11 @@ import ( "github.com/gorilla/handlers" ) +const ( + certFile = "server.crt" + keyFile = "server.key" +) + // Initalize customized logger func init() { log.MeepTextLogInit("Demo-3") @@ -73,7 +79,13 @@ func main() { router := server.NewRouter() methods := handlers.AllowedMethods([]string{"OPTIONS", "DELETE", "GET", "HEAD", "POST", "PUT"}) header := handlers.AllowedHeaders([]string{"content-type"}) - log.Fatal(http.ListenAndServe(port, handlers.CORS(methods, header)(router))) + // Create HTTP client with TLS configuration + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + log.Fatal(http.ListenAndServeTls(port, certFile, keyFile, handlers.CORS(methods, header)(router))) run = false }() diff --git a/examples/demo3/src/backend/server/demo3_service.go b/examples/demo3/src/backend/server/demo3_service.go index 76bec8f951e5cbe7211a848508626eb0faf8ff16..b0f93efd6eed498aae4285853eb9c7f9d8147bae 100644 --- a/examples/demo3/src/backend/server/demo3_service.go +++ b/examples/demo3/src/backend/server/demo3_service.go @@ -780,6 +780,7 @@ func amsNotificationCallback(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } + log.Debug("amsNotificationCallback: amsNotification: ", amsNotification) amsTargetId = amsNotification.TargetAppInfo.AppInstanceId targetDevice := amsNotification.AssociateId[0].Value @@ -970,6 +971,9 @@ func updateAmsSubscription(subscriptionId string, device string, inlineSubscript // Client request to sent context state transfer func sendContextTransfer(notifyUrl string, device string, targetId string) error { + log.Debug("sendContextTransfer: notifyUrl: ", notifyUrl) + log.Debug("sendContextTransfer: device: ", device) + log.Debug("sendContextTransfer: targetId: ", targetId) // Context state transfer var contextState ApplicationContextState @@ -1024,6 +1028,11 @@ func amsSendService(appInstanceId string, device string) (string, error) { // Update the Context Transfer State if the device is present else add ams device func amsSetDevice(amsId string, registerationBody ams.RegistrationInfo, device string, contextState ams.ContextTransferState) (ams.RegistrationInfo, error) { + log.Debug(">>> amsSetDevice: amsId: ", amsId) + log.Debug(">>> amsSetDevice: registerationBody: ", registerationBody) + log.Debug(">>> amsSetDevice: device: ", device) + log.Debug(">>> amsSetDevice: contextState: ", contextState) + var updated bool = false for i := range registerationBody.DeviceInformation { v := ®isterationBody.DeviceInformation[i] diff --git a/examples/demo6/golang/app_instance.yaml b/examples/demo6/golang/app_instance.yaml index 79036f8c125c18c0964422ed7efe7fe524989b41..f60957f6a498ce153ca933e8ed1e941ea48dcac0 100644 --- a/examples/demo6/golang/app_instance.yaml +++ b/examples/demo6/golang/app_instance.yaml @@ -3,7 +3,7 @@ # Set where mec application is running either on MEC Sandbox or AdvantEDGE. Expected fields: sandbox | advantedge mode: 'sandbox' # Set MEC plateform address -sandbox: 'try-mec.etsi.org' +sandbox: 'mec-platform.etsi.org' # Set if sandbox url uses https. Expected fields: true | false https: true # Set the mec platform name demo-6 will run on. Example field: mep1 @@ -13,6 +13,6 @@ localurl: 'http://' # Set host port number of demo-6. Example field: '8093' port: '80' # Callback base URL -callbackUrl: 'http://lab-oai.etsi.org' +callbackUrl: 'http://yanngarcia.ddns.net' # Callback port for listener -callbackPort: '80' +callbackPort: '8547' diff --git a/examples/demo6/golang/main.go b/examples/demo6/golang/main.go index 6e43abef23d566880eb18186cef022157e75729e..321fbcfec1efb0a22e775b90133788f4957ed29d 100644 --- a/examples/demo6/golang/main.go +++ b/examples/demo6/golang/main.go @@ -337,7 +337,7 @@ const ( /** * @brief Clears the terminal screen - * + * * This function clears the terminal screen by printing the ANSI escape sequence * for clearing the screen (\033[2J). */ @@ -347,11 +347,11 @@ func clearScreen() { /** * @brief Displays the main menu and reads user selection - * + * * This function clears the screen and displays a comprehensive menu of available * commands for the MEC Demo6 application. It includes commands for sandbox management, * MEC services, UE management, and various MEC API operations. - * + * * @param message Optional message to display at the bottom of the menu * @return []string Array containing the user's command choice and any parameters */ @@ -396,11 +396,11 @@ func menu(message string) []string { /** * @brief Performs login to the MEC sandbox system - * + * * This function authenticates with the MEC sandbox system using the configured provider. * It initializes global variables and returns the user code and verification URI * needed for the authorization process. - * + * * @return string User code for authorization * @return string Verification URI for authorization * @return error Error if login fails or if already logged in @@ -431,10 +431,10 @@ func login() (string, string, error) { /** * @brief Retrieves the namespace for the current user session - * + * * This function calls the MEC sandbox API to get the namespace associated * with the current user code and stores the sandbox name globally. - * + * * @return string The sandbox name/namespace * @return error Error if the namespace retrieval fails */ @@ -453,7 +453,7 @@ func getNamespace() (string, error) { /** * @brief Performs logout from the MEC sandbox system - * + * * This function performs a complete cleanup of the current session by: * - Deleting all active subscriptions * - Deleting termination subscriptions @@ -462,7 +462,7 @@ func getNamespace() (string, error) { * - Deleting application instances * - Terminating active scenarios * - Logging out from the sandbox - * + * * @return error Error if logout fails or if no active session exists */ func logout() error { @@ -528,10 +528,10 @@ func logout() error { /** * @brief Retrieves the list of available network scenarios - * + * * This function calls the MEC sandbox API to get all available network scenarios * that can be activated in the current sandbox environment. - * + * * @return []client.SandboxNetworkScenario Array of available network scenarios * @return error Error if the scenario list retrieval fails */ @@ -550,7 +550,7 @@ func getListOfScenarios() ([]client.SandboxNetworkScenario, error) { /** * @brief Retrieves detailed information about a specific network scenario - * + * * This function calls the MEC sandbox API to get detailed information about * a specific network scenario identified by its ID. * @@ -574,10 +574,10 @@ func getScenario(scenarioId string) ([]client.Scenario, error) { /** * @brief Activates a network scenario in the MEC sandbox - * + * * This function activates a specific network scenario in the current sandbox * environment. The scenario must be available in the sandbox. - * + * * @param scenarioId The ID of the scenario to activate * @return error Error if scenario activation fails or if no sandbox is available */ @@ -599,10 +599,10 @@ func activateScenario(scenarioId string) error { /** * @brief Terminates an active network scenario in the MEC sandbox - * + * * This function terminates a currently active network scenario in the sandbox * environment. Both sandbox and scenario must be available. - * + * * @param scenarioId The ID of the scenario to terminate * @return error Error if scenario termination fails or if prerequisites are not met */ @@ -626,11 +626,11 @@ func terminateScenario(scenarioId string) error { /** * @brief Retrieves the list of available MEC services in the sandbox - * + * * This function calls the MEC sandbox API to get all available MEC services * that can be used in the current sandbox environment. Requires an active * sandbox and network scenario. - * + * * @return []client.SandboxMecServices Array of available MEC services * @return error Error if service list retrieval fails or prerequisites not met */ @@ -654,11 +654,11 @@ func getListOfMECServices() ([]client.SandboxMecServices, error) { /** * @brief Retrieves the list of MEC application instances in the sandbox - * + * * This function calls the MEC sandbox API to get all application instances * that are currently running in the sandbox environment. Requires an active * sandbox and network scenario. - * + * * @return []client.ApplicationInfo Array of application instance information * @return error Error if application list retrieval fails or prerequisites not met */ @@ -682,11 +682,11 @@ func getListOfMECAppInstIds() ([]client.ApplicationInfo, error) { /** * @brief Creates a new MEC application instance in the sandbox - * + * * This function creates a new application instance in the MEC sandbox environment * using the provided application information. Requires an active sandbox and * network scenario. - * + * * @param appInfo The application information for the new instance * @return error Error if application creation fails or prerequisites not met */ @@ -710,11 +710,11 @@ func createMECAppInstId(appInfo client.ApplicationInfo) error { /** * @brief Deletes the current MEC application instance from the sandbox - * + * * This function deletes the currently active application instance from the * MEC sandbox environment. Requires an active sandbox, network scenario, * and application instance. - * + * * @return error Error if application deletion fails or prerequisites not met */ func deleteMECAppInstId() error { @@ -740,10 +740,10 @@ func deleteMECAppInstId() error { /** * @brief Validates an index against a maximum length - * + * * This function converts a string choice to an integer index and validates * that it is within the valid range (0 to len-1). - * + * * @param choice The string representation of the index * @param len The maximum length/upper bound for validation * @return int The validated integer index @@ -763,11 +763,11 @@ func verify_idx_len(choice string, len int) (int, error) { /** * @brief Retrieves the list of User Equipment (UE) in the sandbox - * + * * This function calls the MEC sandbox API to get all User Equipment instances * that are currently available in the sandbox environment. Requires an active * sandbox and network scenario. - * + * * @return []UeContext Array of UE context information * @return error Error if UE list retrieval fails or prerequisites not met */ @@ -796,11 +796,11 @@ func getListOfUes() (ues []UeContext, err error) { /** * @brief Increases the count of a specific UE type - * + * * This function increases the count of a specific User Equipment type in the * sandbox environment. The maximum count is limited to 4. Requires an active * sandbox and network scenario. - * + * * @param idx The index of the UE type to increase * @return error Error if UE increase fails, prerequisites not met, or maximum reached */ @@ -829,11 +829,11 @@ func increaseUE(idx int) error { /** * @brief Decreases the count of a specific UE type - * + * * This function decreases the count of a specific User Equipment type in the * sandbox environment. The minimum count is 0. Requires an active sandbox * and network scenario. - * + * * @param idx The index of the UE type to decrease * @return error Error if UE decrease fails, prerequisites not met, or minimum reached */ @@ -862,12 +862,12 @@ func decreaseUE(idx int) error { /** * @brief Sends MEC 011 Confirm Ready notification to the MEC platform - * + * * This function implements the MEC 011 Application Support API Confirm Ready operation. * It sends a READY indication to the MEC platform and creates a termination subscription. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.2.4 - * + * * @return string Subscription ID for termination notifications * @return *http.Response HTTP response from the MEC platform * @return error Error if the operation fails or prerequisites are not met @@ -902,13 +902,13 @@ func mec011_send_confirm_ready() (subId string, response *http.Response, err err /** * @brief Creates a termination subscription for MEC 011 application lifecycle management - * + * * This function implements the MEC 011 Application Support API subscription creation * for application termination notifications. It creates a subscription to receive * notifications when the application is about to be terminated. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.3.2 - * + * * @return string Subscription ID for the termination subscription * @return *http.Response HTTP response from the MEC platform * @return error Error if subscription creation fails or prerequisites are not met @@ -957,12 +957,12 @@ func mec011_send_subscribe_termination() (subId string, response *http.Response, /** * @brief Deletes the termination subscription for MEC 011 application lifecycle management - * + * * This function deletes the previously created termination subscription to stop * receiving application termination notifications from the MEC platform. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.3.2 - * + * * @return error Error if subscription deletion fails or prerequisites are not met */ func delete_termination_subscription() (err error) { @@ -996,13 +996,13 @@ func delete_termination_subscription() (err error) { /** * @brief Registers the MEC application with the MEC platform using MEC 011 - * + * * This function implements the MEC 011 Application Support API registration operation. * It registers the application instance with the MEC platform, providing application * information including name, provider, and instance ID. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.2.1 - * + * * @return []byte Response body from the MEC platform * @return *http.Response HTTP response from the MEC platform * @return error Error if registration fails or prerequisites are not met @@ -1054,12 +1054,12 @@ func mec011_send_registration() (body []byte, response *http.Response, err error /** * @brief Deregisters the MEC application from the MEC platform using MEC 011 - * + * * This function implements the MEC 011 Application Support API deregistration operation. * It removes the application instance registration from the MEC platform. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.2.2 - * + * * @return *http.Response HTTP response from the MEC platform * @return error Error if deregistration fails or prerequisites are not met */ @@ -1092,13 +1092,13 @@ func mec011_send_deregistration() (response *http.Response, err error) { /** * @brief Creates a new MEC service using MEC 011 Service Management API - * + * * This function implements the MEC 011 Service Management API service creation operation. * It creates a new service instance for the registered application, providing service * information including name, category, transport details, and endpoints. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.1 - * + * * @return string Resource ID of the created service * @return *http.Response HTTP response from the MEC platform * @return error Error if service creation fails or prerequisites are not met @@ -1178,12 +1178,12 @@ func mec011_create_service() (resId string, response *http.Response, err error) /** * @brief Deletes a MEC service using MEC 011 Service Management API - * + * * This function implements the MEC 011 Service Management API service deletion operation. * It removes the previously created service instance from the MEC platform. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.2 - * + * * @return error Error if service deletion fails or prerequisites are not met */ func mec011_delete_service() (err error) { @@ -1216,12 +1216,12 @@ func mec011_delete_service() (err error) { /** * @brief Retrieves available MEC services using MEC 011 Service Management API - * + * * This function implements the MEC 011 Service Management API service discovery operation. * It retrieves a list of all available MEC services that can be consumed by the application. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.3 - * + * * @return []byte Response body containing the list of available services * @return *http.Response HTTP response from the MEC platform * @return error Error if service discovery fails or prerequisites are not met @@ -1253,11 +1253,11 @@ func mec011_get_mec_services() (body []byte, response *http.Response, err error) /** * @brief Sends HTTP requests to MEC services with proper authentication and headers - * + * * This utility function handles HTTP communication with MEC services, including * request preparation, authentication, and response handling. It supports various * HTTP methods and query parameters. - * + * * @param method HTTP method (GET, POST, DELETE, etc.) * @param path URL path for the request * @param body Request body for POST/PUT operations @@ -1342,13 +1342,13 @@ func send_mec_service_request(method string, path string, body io.Reader, vars u /** * @brief Retrieves UE location information using MEC 013 Location API - * + * * This function implements the MEC 013 Location API user location query operation. * It retrieves the current location information for a specific UE identified by * its address. - * + * * Reference: ETSI GS MEC 013 V3.2.1 (2024-04) Clause 6.1.2.1 - * + * * @param ue_address The address of the UE to query * @return []byte Response body containing UE location information * @return *http.Response HTTP response from the MEC platform @@ -1379,13 +1379,13 @@ func mec013_get_ue_loc(ue_address string) (body []byte, response *http.Response, /** * @brief Creates a UE location subscription using MEC 013 Location API - * + * * This function implements the MEC 013 Location API location event subscription operation. * It creates a subscription to receive location event notifications for a specific UE, * including entering and leaving area events. - * + * * Reference: ETSI GS MEC 013 V3.2.1 (2024-04) Clause 6.1.3.1 - * + * * @param ue_address The address of the UE to monitor * @return []byte Response body containing subscription information * @return *http.Response HTTP response from the MEC platform @@ -1449,13 +1449,13 @@ func mec013_subscribe_ue_loc(ue_address string) (body []byte, response *http.Res /** * @brief Deletes a UE location subscription using MEC 013 Location API - * + * * This function implements the MEC 013 Location API subscription deletion operation. * It removes a previously created location event subscription to stop receiving * location notifications for a specific UE. - * + * * Reference: ETSI GS MEC 013 V3.2.1 (2024-04) Clause 6.1.3.2 - * + * * @param choice Subscription ID to delete * @return *http.Response HTTP response from the MEC platform * @return error Error if subscription deletion fails or prerequisites are not met @@ -1486,13 +1486,13 @@ func mec013_delete_ue_loc_subscription(choice string) (response *http.Response, /** * @brief Retrieves V2X UU unicast provisioning information using MEC 030 V2X API - * + * * This function implements the MEC 030 V2X API UU unicast provisioning query operation. * It retrieves provisioning information for V2X UU unicast communication at a specific * location identified by ECGI. - * + * * Reference: ETSI GS MEC 030 V3.2.1 (2024-04) Clause 6.5.1 - * + * * @return []byte Response body containing V2X UU unicast provisioning information * @return *http.Response HTTP response from the MEC platform * @return error Error if query fails or prerequisites are not met @@ -1520,13 +1520,13 @@ func mec030_get_v2x_uu_unicast_setting() (body []byte, response *http.Response, /** * @brief Creates a V2X message subscription using MEC 030 V2X API - * + * * This function implements the MEC 030 V2X API V2X message subscription operation. * It creates a subscription to receive V2X messages (CAM, DENM, CPM, VAM) from * the MEC platform based on specified filter criteria. - * + * * Reference: ETSI GS MEC 030 V3.2.1 (2024-04) Clause 6.3.5 - * + * * @return []byte Response body containing subscription information * @return *http.Response HTTP response from the MEC platform * @return error Error if subscription creation fails or prerequisites are not met @@ -1576,13 +1576,13 @@ func mec030_subscribe_v2x_messages() (body []byte, response *http.Response, err /** * @brief Deletes a V2X message subscription using MEC 030 V2X API - * + * * This function implements the MEC 030 V2X API subscription deletion operation. * It removes a previously created V2X message subscription to stop receiving * V2X messages from the MEC platform. - * + * * Reference: ETSI GS MEC 030 V3.2.1 (2024-04) Clause 6.3.6 - * + * * @param choice Subscription ID to delete * @return *http.Response HTTP response from the MEC platform * @return error Error if subscription deletion fails or prerequisites are not met @@ -1616,13 +1616,13 @@ func mec030_delete_v2x_messages_subscription(choice string) (response *http.Resp // https://www.latlong.net/degrees-minutes-seconds-to-decimal-degrees /** * @brief Provides predicted QoS information using MEC 030 V2X API - * + * * This function implements the MEC 030 V2X API predicted QoS provision operation. * It provides predicted QoS information for a specific route defined by latitude, * longitude coordinates and timestamps. - * + * * Reference: ETSI GS MEC 030 V3.2.1 (2024-04) Clause 6.5.15 - * + * * @param latitudes Comma-separated list of latitude values * @param longitudes Comma-separated list of longitude values * @param timestamps Comma-separated list of timestamp values @@ -1703,13 +1703,13 @@ func mec030_predicted_qos(latitudes string, longitudes string, timestamps string /** * @brief Retrieves the list of federation systems using MEC 040 Federation API - * + * * This function implements the MEC 040 Federation API system discovery operation. * It retrieves a list of all available federation systems that can be accessed * through the MEC platform. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.2.1 - * + * * @return []byte Response body containing the list of federation systems * @return *http.Response HTTP response from the MEC platform * @return error Error if system discovery fails or prerequisites are not met @@ -1739,13 +1739,13 @@ func mec40_get_systems_list() (body []byte, response *http.Response, err error) /** * @brief Retrieves the list of services for a specific federation system using MEC 040 - * + * * This function implements the MEC 040 Federation API service discovery operation. * It retrieves a list of all available services provided by a specific federation * system. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.2.2 - * + * * @param choice System ID to query for services * @return []byte Response body containing the list of federation services * @return *http.Response HTTP response from the MEC platform @@ -1778,13 +1778,13 @@ func mec40_get_services_list(choice string) (body []byte, response *http.Respons /** * @brief Retrieves detailed information about a specific federation service using MEC 040 - * + * * This function implements the MEC 040 Federation API individual service query operation. * It retrieves detailed information about a specific service provided by a federation * system. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.2.3 - * + * * @param systemId The ID of the federation system * @param serviceId The ID of the service to query * @return []byte Response body containing detailed service information @@ -1818,13 +1818,13 @@ func mec40_get_service_list(systemId string, serviceId string) (body []byte, res /** * @brief Creates a system update notification subscription using MEC 040 Federation API - * + * * This function implements the MEC 040 Federation API subscription creation operation. * It creates a subscription to receive notifications about updates to federation * systems and their services. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.3.1 - * + * * @return []byte Response body containing subscription information * @return *http.Response HTTP response from the MEC platform * @return error Error if subscription creation fails or prerequisites are not met @@ -1870,13 +1870,13 @@ func mec40_create_subscription() (body []byte, response *http.Response, err erro /** * @brief Retrieves federation subscription information using MEC 040 Federation API - * + * * This function implements the MEC 040 Federation API subscription query operation. * It retrieves information about federation subscriptions, either all subscriptions * or a specific subscription by ID. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.3.2 - * + * * @param choice Array containing optional subscription ID for individual query * @return []byte Response body containing subscription information * @return *http.Response HTTP response from the MEC platform @@ -1910,13 +1910,13 @@ func mec40_get_subscriptions(choice []string) (body []byte, response *http.Respo /** * @brief Deletes a federation subscription using MEC 040 Federation API - * + * * This function implements the MEC 040 Federation API subscription deletion operation. * It removes a previously created federation subscription to stop receiving * system update notifications. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.3.3 - * + * * @param choice Subscription ID to delete * @return *http.Response HTTP response from the MEC platform * @return error Error if subscription deletion fails or prerequisites are not met @@ -1948,13 +1948,13 @@ func mec40_delete_subscriptions(choice string) (response *http.Response, err err /** * @brief Retrieves all available CAPIF services - * + * * This function implements the CAPIF API service discovery operation. * It retrieves a list of all available service APIs that can be consumed * through the CAPIF interface. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.2.1 - * + * * @return []byte Response body containing the list of available CAPIF services * @return *http.Response HTTP response from the CAPIF platform * @return error Error if service discovery fails or prerequisites are not met @@ -1984,13 +1984,13 @@ func capif_get_all_svcs() (body []byte, response *http.Response, err error) { /** * @brief Retrieves CAPIF services for the current application instance - * + * * This function implements the CAPIF API published service query operation. * It retrieves a list of service APIs that are published by the current * application instance. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.2.2 - * + * * @return []byte Response body containing the list of published services * @return *http.Response HTTP response from the CAPIF platform * @return error Error if service query fails or prerequisites are not met @@ -2022,12 +2022,12 @@ func capif_get_svc() (body []byte, response *http.Response, err error) { /** * @brief Creates a new CAPIF service (Not implemented) - * + * * This function is a placeholder for the CAPIF API service creation operation. * Currently not implemented in this demo application. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.2.3 - * + * * @return []byte Response body (not implemented) * @return *http.Response HTTP response (not implemented) * @return error Error indicating not implemented @@ -2049,12 +2049,12 @@ func capif_create_svc() (body []byte, response *http.Response, err error) { /** * @brief Deletes a CAPIF service (Not implemented) - * + * * This function is a placeholder for the CAPIF API service deletion operation. * Currently not implemented in this demo application. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.2.4 - * + * * @param choice Service ID to delete * @return *http.Response HTTP response (not implemented) * @return error Error indicating not implemented @@ -2079,12 +2079,12 @@ func capif_delete_svc(choice string) (response *http.Response, err error) { /** * @brief Creates a CAPIF subscription (Not implemented) - * + * * This function is a placeholder for the CAPIF API subscription creation operation. * Currently not implemented in this demo application. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.3.1 - * + * * @return []byte Response body (not implemented) * @return *http.Response HTTP response (not implemented) * @return error Error indicating not implemented @@ -2110,12 +2110,12 @@ func capif_create_subscription() (body []byte, response *http.Response, err erro /** * @brief Retrieves CAPIF subscriptions (Not implemented) - * + * * This function is a placeholder for the CAPIF API subscription query operation. * Currently not implemented in this demo application. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.3.2 - * + * * @return []byte Response body (not implemented) * @return *http.Response HTTP response (not implemented) * @return error Error indicating not implemented @@ -2137,12 +2137,12 @@ func capif_get_subscriptions() (body []byte, response *http.Response, err error) /** * @brief Deletes a CAPIF subscription (Not implemented) - * + * * This function is a placeholder for the CAPIF API subscription deletion operation. * Currently not implemented in this demo application. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.3.3 - * + * * @param choice Subscription ID to delete * @return *http.Response HTTP response (not implemented) * @return error Error indicating not implemented @@ -2167,11 +2167,11 @@ func capif_delete_subscriptions(choice string) (response *http.Response, err err /** * @brief Returns the current status of the MEC application - * + * * This utility function provides a comprehensive status overview of the current * MEC application state, including sandbox information, application registration, * service creation, and active subscriptions. - * + * * @return string Formatted status string containing current application state */ func app_status() (resp string) { @@ -2205,11 +2205,11 @@ func app_status() (resp string) { /** * @brief Extracts subscription ID from a subscription URL - * + * * This utility function parses a subscription URL to extract the subscription ID * using regular expressions. It matches the subscription ID pattern from the URL * and returns the extracted identifier. - * + * * @param base_url The base URL for the subscription endpoint * @param subscription_url The complete subscription URL containing the ID * @return string The extracted subscription ID @@ -2235,10 +2235,10 @@ func extract_subscription_id(base_url string, subscription_url string) (string, /** * @brief Deletes a subscription by URL - * + * * This utility function deletes a subscription by sending a DELETE request to * the specified URL and removes the subscription from the local tracking list. - * + * * @param url The URL of the subscription to delete * @return *http.Response HTTP response from the deletion operation * @return error Error if deletion fails @@ -2259,11 +2259,11 @@ func delete_subscription(url string) (response *http.Response, err error) { /** * @brief Removes a subscription from the local tracking list - * + * * This utility function removes a subscription from the internal subscriptions * array based on the provided URL. It searches for the subscription and removes * it from the list. - * + * * @param url The URL of the subscription to remove from the list */ func delete_subscription_from_list(url string) { @@ -2278,13 +2278,13 @@ func delete_subscription_from_list(url string) { /** * @brief Handles MEC 013 location notification callbacks - * + * * This function serves as the HTTP handler for MEC 013 location event notifications. * It receives location event notifications from the MEC platform and responds * with HTTP 200 OK. - * + * * Reference: ETSI GS MEC 013 V3.2.1 (2024-04) Clause 6.1.3.1 - * + * * @param w HTTP response writer * @param r HTTP request containing the location notification */ @@ -2295,13 +2295,13 @@ func mec013_notification(w http.ResponseWriter, r *http.Request) { /** * @brief Handles MEC 030 V2X message notification callbacks - * + * * This function serves as the HTTP handler for MEC 030 V2X message notifications. * It receives V2X message notifications from the MEC platform and responds * with HTTP 200 OK. - * + * * Reference: ETSI GS MEC 030 V3.2.1 (2024-04) Clause 6.3.5 - * + * * @param w HTTP response writer * @param r HTTP request containing the V2X message notification */ @@ -2312,13 +2312,13 @@ func v2x_msg_notification(w http.ResponseWriter, r *http.Request) { /** * @brief Handles MEC 040 federation notification callbacks - * + * * This function serves as the HTTP handler for MEC 040 federation system update * notifications. It receives federation notifications from the MEC platform * and responds with HTTP 200 OK. - * + * * Reference: ETSI GS MEC 040 V3.2.1 (2024-04) Clause 6.1.3.1 - * + * * @param w HTTP response writer * @param r HTTP request containing the federation notification */ @@ -2329,13 +2329,13 @@ func fed_notification(w http.ResponseWriter, r *http.Request) { /** * @brief Handles CAPIF notification callbacks - * + * * This function serves as the HTTP handler for CAPIF service notifications. * It receives service notifications from the CAPIF platform and responds * with HTTP 200 OK. - * + * * Reference: ETSI TS 103 384 V3.2.1 (2024-04) Clause 6.1.3.1 - * + * * @param w HTTP response writer * @param r HTTP request containing the CAPIF notification */ @@ -2346,13 +2346,13 @@ func capif_notification(w http.ResponseWriter, r *http.Request) { /** * @brief Handles MEC 011 service statistic requests - * + * * This function serves as the HTTP handler for MEC 011 service statistic requests. * It provides service statistics information when requested by the MEC platform * and responds with HTTP 200 OK. - * + * * Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.1 - * + * * @param w HTTP response writer * @param r HTTP request for service statistics */ @@ -2363,18 +2363,18 @@ func mec011_service_statistic_get(w http.ResponseWriter, r *http.Request) { /** * @brief Main entry point for the MEC Demo6 application - * + * * This function initializes the MEC Demo6 application, loads configuration, * sets up HTTP servers for callbacks, and starts the interactive menu system. * It handles graceful shutdown and cleanup of resources. - * + * * The application demonstrates the usage of various MEC APIs including: * - MEC 011 Application Support and Service Management * - MEC 013 Location Services * - MEC 030 V2X Services * - MEC 040 Federation Services * - CAPIF Service APIs - * + * * @return void */ func main() { @@ -2503,11 +2503,11 @@ func main() { /** * @brief Loads configuration from YAML file using Viper - * + * * This function loads application configuration from a YAML file using the * Viper configuration management library. It supports environment variable * overrides and automatic configuration file discovery. - * + * * @param path Directory path containing the configuration file * @param name Configuration file name (without extension) * @return Config Loaded configuration structure @@ -2534,11 +2534,11 @@ func LoadConfig(path string, name string) (config Config, err error) { /** * @brief Processes user menu choices and executes corresponding operations - * + * * This function handles all user menu selections and executes the appropriate * operations based on the command choice. It supports operations for sandbox * management, MEC services, UE management, and various MEC API operations. - * + * * @param choice Array containing the user's command choice and parameters * @return string Status message describing the result of the operation */ diff --git a/examples/demo6/python/notebook/.ipynb_checkpoints/CAPIF_And_ETSI_MEC_Tutorial-checkpoint.ipynb b/examples/demo6/python/notebook/.ipynb_checkpoints/CAPIF_And_ETSI_MEC_Tutorial-checkpoint.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d20e87f4c913ec30d3219d8939e3abc5799324eb --- /dev/null +++ b/examples/demo6/python/notebook/.ipynb_checkpoints/CAPIF_And_ETSI_MEC_Tutorial-checkpoint.ipynb @@ -0,0 +1,2243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "44TomlvPCGTe" + }, + "source": [ + "# Using ETSI MEC profile of CAPIF in CAPIF application\n", + "\n", + "## Introduction\n", + "\n", + "3GPP CAPIF (Common API Framework) is a standardized API management framework designed to enable a unified northbound API approach across 3GPP network functions (see 3GPP TS 23.222 version 18.6.0 Release 18/ETSI TS 123 222 V18.8.0 (2025-07) and 3GPP TS 29.222 version 18.6.0 Release 18/ETSI TS 129 222 V18.7.0 (2025-01)).\n", + "\n", + "This tutorial introduces the step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile as described in ETSI GS MEC 011 (V3.2.1) Clause 9. The cartoon below illustrate the \n", + "\n", + "![image](images/capif.png)\n", + "\n", + "**Note:** It uses the ETSI MEC Sandbox simulator.\n", + "\n", + "

\n", + " Note: These source code examples are simplified and ignore return codes and error checks to a large extent. We do this to highlight how to use the MEC Sandbox API and the different MEC satndards and reduce unrelated code.\n", + "A real-world application will of course properly check every return value and exit correctly at the first serious error.\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4DpxwmiomELg" + }, + "source": [ + "## The basics of developing a CAPIF application\n", + "\n", + "\n", + "
\n", + " Note: The sub-paragraph 'Putting everything together' is a specific paragraph where all the newly features introduced in the main paragraph are put together to create an executable block of code. It is possible to skip this block of code by removing the comment character (#) on first line of this block of code.\n", + "
\n", + "\n", + "Before going to create our CAPIF application skeleton, the following steps shall be done:\n", + "\n", + "1) Apply the python imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1gjo-NM6hD1k" + }, + "outputs": [], + "source": [ + "from __future__ import division # Import floating-point division (1/4=0.25) instead of Euclidian division (1/4=0)\n", + "\n", + "import os\n", + "import sys\n", + "import re\n", + "import logging\n", + "import threading\n", + "import time\n", + "import json\n", + "import uuid\n", + "import base64\n", + "\n", + "import pprint\n", + "\n", + "import requests\n", + "\n", + "from http import HTTPStatus\n", + "from http.server import BaseHTTPRequestHandler, HTTPServer\n", + "\n", + "try:\n", + " import urllib3\n", + "except ImportError:\n", + " raise ImportError('Swagger python client requires urllib3.')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j9wDIe9IEUQz" + }, + "source": [ + "The following imports are required to support the security aspects such as certificates management, signatures..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "xb4ReBZZEVLB" + }, + "outputs": [], + "source": [ + "!pip3 install pyOpenSSL==25.0.0\n", + "\n", + "try:\n", + " from OpenSSL.SSL import FILETYPE_PEM\n", + " from OpenSSL.crypto import (dump_certificate_request, dump_privatekey, load_publickey, PKey, TYPE_RSA, X509Req, dump_publickey)\n", + "except ImportError:\n", + " raise ImportError('OpenSSL package not found.')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DrPJzD14nLas" + }, + "source": [ + "2) Initialize of the global constants (cell 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rNibZWiBitPE" + }, + "outputs": [], + "source": [ + "USE_OCF_SANDBOX = False # Set tot True if using OCF sandbox instead of lab-oai.etsi.org\n", + "\n", + "if not USE_OCF_SANDBOX:\n", + " REGISTER_HOSTNAME = 'lab-oai.etsi.org' # OCF register server \n", + " REGISTER_PORT = 31120 # 31120\n", + " REGISTER_USER = 'admin' # Basic AUTH for registration\n", + " REGISTER_PASSWORD = 'password123' # Basic AUTH for registration\n", + " CAPIF_HOSTNAME = 'lab-oai.etsi.org'\n", + " USER_PASSWORD = 'password123'\n", + "else:\n", + " REGISTER_HOSTNAME = 'register-opencapif.etsi.org' # capif-prev.mobilesandbox.cloud\n", + " REGISTER_PORT = 443 # 31120\n", + " REGISTER_USER = 'yann' # Basic AUTH for registration\n", + " REGISTER_PASSWORD = 'yannpass123' # Basic AUTH for registration\n", + " CAPIF_HOSTNAME = 'opencapif.etsi.org' # OCF Server\n", + " USER_PASSWORD = 'yannpass123'\n", + "\n", + "CAPIF_PORT = 443\n", + "\n", + "TRY_MEC_URL = 'try-mec.etsi.org' # MEC Sandbox URL\n", + "#TRY_MEC_URL = 'mec-platform.etsi.org'\n", + "TRY_MEC_PORT = 443\n", + "TRY_MEC_SESSION_ID = 'sbxgs9x587' # MEC Sandbox identifier\n", + "#TRY_MEC_SESSION_ID = 'sbxykqjr17'\n", + "TRY_MEC_PLTF = 'mep1' # MEC Platform identifier (depending of the network scenario loaded)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MOa9g-NMnpod" + }, + "source": [ + "3) Setup the logger instance and the HTTP REST API (cell 4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-cuxWhfantSw" + }, + "outputs": [], + "source": [ + "# Initialize the logger\n", + "logger = logging.getLogger(__name__)\n", + "logger.setLevel(logging.DEBUG)\n", + "logging.basicConfig(filename='/tmp/' + time.strftime('%Y%m%d-%H%M%S') + '.log')\n", + "l = logging.StreamHandler()\n", + "l.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))\n", + "logger.addHandler(l)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D67Aq0vujB0q" + }, + "source": [ + "4) Setup the global variables (cell 5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7RC7UY-0oACq" + }, + "outputs": [], + "source": [ + "# Initialize the global variables\n", + "ca_root = \"\" # The CAPIF root certificate\n", + "ccf_api_onboarding_url = \"\" #\n", + "ccf_publish_url = \"\" # The CAPIF publish API endpoint\n", + "ccf_discover_url = \"\" # The CAPIF discovery endpoint\n", + "ccf_security_url = \"\" # The CAPIF security endpoint\n", + "ccf_onboarding_url = \"\" # The CAPIF onboarding endpoint\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2YvSVMClhPJT" + }, + "source": [ + "To enable the Automatic Debugger Calling, uncomment the code bellow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "OQjYWHgnYM4G" + }, + "outputs": [], + "source": [ + "#!pip3 install ipdb\n", + "#import ipdb\n", + "#%pdb on\n", + "# Use the command ipdb.set_trace() to set a breakpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1fMmXWk9jLDX" + }, + "source": [ + "## Create our first CAPIF application\n", + "\n", + "The first step to develop a MEC application is to create the application skeleton which contains the minimum steps below:\n", + "\n", + "- Login to instanciate a MEC Sandbox\n", + "- Logout to delete a existing MEC Sandbox" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rtAVXZayoQRx" + }, + "source": [ + "#### Login\n", + "\n", + "The login operation is required by ETSI TS 123 222 V18.8.0 (2025-07) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.8.0 (2025-07)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Ad8g1no-pH7i" + }, + "outputs": [], + "source": [ + "def process_login() -> tuple:\n", + " \"\"\"\n", + " Logs in to the CAPIF server.\n", + " :return A dictionary containing the login response, or None if login fails\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> process_login')\n", + "\n", + " if USE_OCF_SANDBOX:\n", + " logger.debug('process_login: Not applicable')\n", + " return '', ''\n", + "\n", + " try:\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/login'\n", + " logger.debug('process_login: url=' + url)\n", + " auth_string = f\"{REGISTER_USER}:{REGISTER_PASSWORD}\"\n", + " encoded_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')\n", + " headers = {'Content-Type': 'application/json', 'Authorization': f'Basic {encoded_auth}'}\n", + " logger.debug('process_login (step1): headers: ' + str(headers))\n", + " response = requests.post(url, headers=headers, verify=False)\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('process_login (step2): result: ' + str(response.json()))\n", + " if response.status_code != 200:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return None, None\n", + " tokens = json.loads(response.text)\n", + " return tokens['refresh_token'], tokens['access_token']\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Login failed: {e}\")\n", + "\n", + " return None, None\n", + " # End of function process_login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8Cw5MBc-st1e" + }, + "source": [ + "### Logout\n", + "\n", + "The logout operation is required by ETSI TS 123 222 V18.8.0 (2025-07) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.8.0 (2025-07)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XmyLOuFasuvU" + }, + "outputs": [], + "source": [ + "def process_logout():\n", + " \"\"\"\n", + " Logs out from the CAPIF server\n", + " Nothing to do\n", + " \"\"\"\n", + " pass\n", + " # End of function process_logout" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mCKT-ntspnsM" + }, + "source": [ + "### Putting everything together\n", + "Now, it is time now to create the our first iteration of our CAPIF/MEC application. Here the logic is:\n", + "* Login\n", + "* Print obtained tokens\n", + "* Logout\n", + "* Check that logout is effective" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XYC8PnDUpvui" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the first sprint of our CAPIF application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Logout\n", + " - Check that logout is effective\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if USE_OCF_SANDBOX:\n", + " return\n", + " if refresh_token is None:\n", + " logger.error(\"Login unsuccessful\")\n", + " return\n", + "\n", + " # Print obtained tokens\n", + " logger.debug(\"Login successful: admin_token=\" + admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective')\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rTcvGY5T1pZJ" + }, + "source": [ + "## Create the API Provider\n", + "\n", + "The next step is to create a new user associated to our CAPIF application to obtain a user UUID. It will be used to genereate ceertificates to be used during TLS mutual authentication and API onboarding and offboarding for instance.\n", + "\n", + "**Note:** It is required by ETSI TS 123 222 V18.8.0 (2025-07) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.8.0 (2025-07).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ysxZ8sIiLLgw" + }, + "source": [ + "### Creating a new user\n", + "\n", + "The cell below provides an implementation for this user creation.\n", + "\n", + "**Note:** To improve this code, the user profile shlould be fully parametrized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Jq-9_sLI8WgW" + }, + "outputs": [], + "source": [ + "def create_user(p_admin_token: str) -> tuple:\n", + " \"\"\"\n", + " Creates a new user.\n", + " :return: The user UUID on success, None otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> create_user')\n", + "\n", + " if USE_OCF_SANDBOX:\n", + " logger.debug('create_user is not applicable against OCF Sandbox')\n", + " return ('', '')\n", + "\n", + " try:\n", + " user_name = str(uuid.uuid1())\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/createUser'\n", + " logger.debug('create_user: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_admin_token}\n", + " logger.debug('create_user (step1): headers: ' + str(headers))\n", + " data = {\n", + " 'username': user_name,\n", + " 'password': USER_PASSWORD,\n", + " 'enterprise': 'ETSI',\n", + " 'country': 'France',\n", + " 'email': 'ocf@etsi.org',\n", + " 'purpose': 'Tutorial on MEC/OpenCAPIF',\n", + " 'phone_number': \"+330405060708\",\n", + " 'company_web': 'www.etsi.org',\n", + " 'description': 'A step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile'\n", + " }\n", + " response = requests.post(url, headers=headers, data=json.dumps(data), verify=False)\n", + " logger.debug('create_user (step2): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return ()\n", + " tokens = json.loads(response.text)\n", + " return (user_name, tokens['uuid'])\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Error creating user: {e}\")\n", + "\n", + " return ()\n", + " # End of function create_user" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ut3CLrRUFT5o" + }, + "source": [ + "### Deleting an existing User\n", + "\n", + "Before to terminate our CAPIF application, we have to clean up the resources. So, a function to delete a created user is required." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WRIdwNMNFrdC" + }, + "outputs": [], + "source": [ + "def delete_user(p_user_uuid: str, p_admin_token: str) -> int:\n", + " \"\"\"\n", + " Deletes a user.\n", + " :param p_user_uuid: The user UUID\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> delete_user')\n", + "\n", + " if USE_OCF_SANDBOX:\n", + " logger.debug('delete_user is not applicable against OCF Sandbox')\n", + " return 0\n", + "\n", + " try:\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/deleteUser/' + p_user_uuid\n", + " logger.debug('delete_user: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_admin_token}\n", + " response = requests.delete(url, headers=headers, verify=False)\n", + " logger.debug('delete_user: response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " return 0\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Error creating user: {e}\")\n", + "\n", + " return -1\n", + " # End of function delete_user" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IAh9tN25-82V" + }, + "source": [ + "### Putting everything together\n", + "It is time now to create the our second iteration of our CAPIF/MEC application.\n", + "\n", + "The sequence is the following:\n", + "* Login\n", + "* Print obtained tokens\n", + "* Create a new user\n", + "* Print the user UUID\n", + "* Delete the newly created user\n", + "* Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1M_x2I1B_Crp" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our CAPIF/MEC application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Create a new user\n", + " - Print the user UUID\n", + " - Delete the newly created user\n", + " - Logout\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", + " return\n", + "\n", + " # Print obtained tokens\n", + " logger.debug(\"Login successful: admin_token=\" + admin_token)\n", + "\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if USE_OCF_SANDBOX:\n", + " return\n", + " if len(user_uuid) == 0:\n", + " return\n", + "\n", + " # Print User UUID\n", + " logger.debug(\"User successfully created: user_uuid=\" + user_uuid)\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Delete the newly created user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f896qBJOjMuz" + }, + "source": [ + "## Getting security materials\n", + "\n", + "The purpose is to retrieves peer certificates for the TLS mutual authentication purpose and the JWT token to onboarding and offboarding APIs.\n", + "The following information is retrived:\n", + "- The root certificate\n", + "- An access token which will be used for onboarding and offboarding APIs\n", + "- The URLs for the different CAPIF endpoints:\n", + " * API onbording endpoint\n", + " * API discovery endpoint\n", + " * API publish endpoint\n", + " * Security endpoint\n", + "\n", + "This operation needs the user name and the user password used in previous [chapter](#create_the_invoker_/_provider).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lC2JAah7LWLp" + }, + "source": [ + "### Getting certificates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "1glmqNSRK1cH" + }, + "outputs": [], + "source": [ + "def get_auth(p_user_name: str, p_user_password: str) -> dict:\n", + " \"\"\"\n", + " Gets the authentication information.\n", + " :param The user name\n", + " :param The user password\n", + " :return A dictionary containing the authentication information on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> get_auth')\n", + "\n", + " try:\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/getauth'\n", + " logger.debug('get_auth: url=' + url)\n", + " auth_string = f\"{p_user_name}:{p_user_password}\"\n", + " encoded_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')\n", + " headers = {'Content-Type': 'application/json', 'Authorization': f'Basic {encoded_auth}'}\n", + " logger.debug('get_auth (step1): headers: ' + str(headers))\n", + " response = requests.get(url, headers=headers, verify=False)\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('get_auth (step2): result: ' + str(response.json()))\n", + " if response.status_code != 200:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " auth = json.loads(response.text)\n", + " return auth\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"get_auth failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function get_auth" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BUw-VS1WLb7i" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Now, it is time now to create the our third iteration of our MEC application. Here the logic is:\n", + "\n", + "- Login\n", + "- Create the user\n", + "- Get the information to use CAPIF (security materials & URLs)\n", + "- Print the information to use CAPI\n", + "- Delete the user\n", + "- Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "J002Vuz2OIKl" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the first sprint of our skeleton of our CAPIF application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Create a new user\n", + " - Get the information to use CAPIF (security materials & URLs)\n", + " - Print the information to use CAPI\n", + " - Delete the newly created user\n", + " - Logout\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_publish_url, ccf_discover_url, ccf_security_url, ccf_onboarding_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", + " return\n", + "\n", + " # Get auth & URLs\n", + " auth = None\n", + " user_name = \"\"\n", + " user_uuid = \"\"\n", + " if USE_OCF_SANDBOX:\n", + " auth = get_auth(REGISTER_USER, REGISTER_PASSWORD)\n", + " else:\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " return\n", + "\n", + " # Print the authentication information\n", + " logger.debug(\"Authentication information=\" + str(auth))\n", + " access_token = auth['access_token']\n", + " ca_root = auth['ca_root']\n", + " ccf_api_onboarding_url = auth['ccf_api_onboarding_url']\n", + " ccf_discover_url = auth['ccf_discover_url']\n", + " ccf_onboarding_url = auth['ccf_onboarding_url']\n", + " ccf_publish_url = auth['ccf_publish_url']\n", + " ccf_security_url = auth['ccf_security_url']\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Delete the newly created user\n", + " if not USE_OCF_SANDBOX:\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oNhnnDhjjOd7" + }, + "source": [ + "## Onboarding and offboarding APIs\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "K6i4ktfM1xFQ" + }, + "source": [ + "### Generate certificates\n", + "\n", + "Until now, all HTTPS exchanges were done with the the 'verify' attribute of the HTTP reques set to False. It means that the TLS mutual authentication was disabled.\n", + "\n", + "Fo the process of onboarding and offboarding APIs, the TLS mutual authentication is required. We already got the peer certificate to verify peer but we need to generate our own certificate to be verified by the CAPIF server. This is the purpose of the following functions to generate the public/private keys and generate a CSR and request certificates for each of the three functions AMF, AEF and APF.\n", + "\n", + "**Refer to:** ETSI TS 129 222 V18.8.0 (2025-07) Clauses 5.11 CAPIF_API_Provider_Management and 8.9 CAPIF_API_Provider_Management_API\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gEIS3iAH2D4t" + }, + "outputs": [], + "source": [ + "def generate_csr(p_cn: str, p_org: str, p_country: str) -> tuple:\n", + " \"\"\"\n", + " To generate the CSR and generate the dumps\n", + " :param p_cn: The common name\n", + " :param p_org: The organization\n", + " :param p_country: The country\n", + " :return: The CSR and the private keys on success, None otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> generate_csr')\n", + "\n", + " # Generate the public/private key\n", + " key = PKey()\n", + " key.generate_key(TYPE_RSA, 2048)\n", + "\n", + " # Generate the CSR\n", + " req = X509Req()\n", + " req.get_subject().CN = p_cn\n", + " req.get_subject().O = p_org\n", + " req.get_subject().C = p_country\n", + " req.set_pubkey(key)\n", + " req.sign(key, 'sha256')\n", + "\n", + " # Generate the dumps\n", + " csr_request = dump_certificate_request(FILETYPE_PEM, req)\n", + " private_key = dump_privatekey(FILETYPE_PEM, key)\n", + " logger.debug('generate_csr: PrivKey: ' + str(private_key))\n", + "\n", + " return (csr_request, private_key)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "F2-W0a5S3snI" + }, + "source": [ + "**Note:** The function above can be improved using parameter for the SHA, and the signature/encryption algorithm." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1HyqrdUz-uzn" + }, + "source": [ + "### Onboard the API provider\n", + "\n", + "The purpose here is to get certificates from CAPIF in order to export our APIs for the different functions:\n", + "- AMF: API Management Function\n", + "- AEF: API Exposing Function\n", + "- APF: API Publishing Function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6cCn1vKLGe0k" + }, + "outputs": [], + "source": [ + "def onboard_provider(p_name: str, p_access_token: str) -> dict:\n", + " \"\"\"\n", + " To onboard the provider.\n", + " :param p_name: The name of the provider\n", + " :return: A dictionary containing security material for each CAPIF endpoint on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, access_token\n", + "\n", + " logger.debug('>>> onboard_provider')\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_api_onboarding_url\n", + " logger.debug('onboard_provider: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_access_token}\n", + " logger.debug('onboard_provider (step1): headers: ' + str(headers))\n", + " # Build the list of certificate request for the three endpoints\n", + " l = []\n", + " amf_csr_request, amf_private_key = generate_csr(\"AMF\", \"ETSI\", \"Fr\")\n", + " amf_entry = {\n", + " 'regInfo': {\n", + " 'apiProvPubKey': amf_csr_request.decode(\"utf-8\")\n", + " },\n", + " 'apiProvFuncRole': 'AMF'\n", + " }\n", + " l.append(amf_entry)\n", + " aef_csr_request, aef_private_key = generate_csr(\"AEF\", \"ETSI\", \"Fr\")\n", + " aef_entry = {\n", + " 'regInfo': {\n", + " 'apiProvPubKey': aef_csr_request.decode(\"utf-8\")\n", + " },\n", + " 'apiProvFuncRole': 'AEF'\n", + " }\n", + " l.append(aef_entry)\n", + " apf_csr_request, apf_private_key = generate_csr(\"APF\", \"ETSI\", \"Fr\")\n", + " apf_entry = {\n", + " 'regInfo': {\n", + " 'apiProvPubKey': apf_csr_request.decode(\"utf-8\")\n", + " },\n", + " 'apiProvFuncRole': 'APF'\n", + " }\n", + " l.append(apf_entry)\n", + " # Build the request body\n", + " data = {\n", + " 'apiProvFuncs': l,\n", + " 'apiProvDomInfo': p_name,\n", + " 'suppFeat': 'fff',\n", + " 'failReason': 'string',\n", + " 'regSec': p_access_token\n", + " }\n", + " logger.debug('onboard_provider (step2): body: ' + str(data))\n", + " response = requests.post(url, headers=headers, data=json.dumps(data), verify=False)\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('onboard_provider (step3): result: ' + str(response.json()))\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " # Add an entry for CSRs and private keys for future usage\n", + " res['csr'] = {\n", + " 'amf': [amf_csr_request, amf_private_key],\n", + " 'aef': [aef_csr_request, aef_private_key],\n", + " 'apf': [apf_csr_request, apf_private_key]\n", + " }\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"onboard_provider failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function onboard_provider" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yP6ZytijFxKG" + }, + "source": [ + "### Offboard the API provider\n", + "\n", + "The purpose is to offboard the API provider from the CAPIF server. Here, the certificate and the private key of the AMF endpoint are required (TLS mutual authentication)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "rbpNr26tF2gr" + }, + "outputs": [], + "source": [ + "def offboard_provider(p_api_provider_id: str, p_bundle: tuple) -> list:\n", + " \"\"\"\n", + " To offboard the API provider.\n", + " :param p_api_provider_id: The identifier of the API provider\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " :return: A list containing the files created for the TLS mutual authentication operations on success, or an empty list otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, ca_root\n", + "\n", + " logger.debug('>>> offboard_provider')\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_api_onboarding_url + '/' + p_api_provider_id\n", + " logger.debug('offboard_provider: url=' + url)\n", + " headers = {'Content-Type': 'application/json'}\n", + " logger.debug('offboard_provider (step1): headers: ' + str(headers))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return []\n", + " logger.debug('offboard_provider (step2): bundle: ' + str(bundle))\n", + " response = requests.delete(url, headers=headers, cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " logger.debug('offboard_provider (step3): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 204:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return []\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"offboard_provider failed: {e}\")\n", + " return []\n", + "\n", + " return bundle\n", + " # End of function offboard_provider\n", + "\n", + "def store_certificate_2_files(p_certificate, p_private_key, p_ca_root) -> list:\n", + " \"\"\"\n", + " Save certificate and key into files\n", + " :param p_certificate:\n", + " :param p_private_key:\n", + " :param p_ca_root:\n", + " :return: A list of file paths on success, an empty list otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> store_certificate_2_files')\n", + " try:\n", + " with open(\"p_crt.crt\", \"w\") as f:\n", + " f.write(p_certificate)\n", + " with open(\"p_key.key\", \"w\") as f:\n", + " f.write(p_private_key.decode('utf-8'))\n", + " with open(\"ca_root.pem\", \"w\") as f:\n", + " f.write(p_ca_root)\n", + " return [\"p_crt.crt\", \"p_key.key\", \"ca_root.pem\"]\n", + " except Exception as e:\n", + " logger.error(f\"An error occurred: {e}\")\n", + "\n", + " return []\n", + " # End of function store_certificate_2_files\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wmvJSK8I13XD" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Now, it is time now to create the our third iteration of our CAPIF/MEC application. Here the logic is:\n", + "\n", + "- Login\n", + "- Create the user\n", + "- Get the information to use CAPIF (security materials & URLs)\n", + "- Onboard the provider\n", + "- Print certificates for each function\n", + "- Delete the user\n", + "- Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "EDcPUuNEM26H" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the third sprint of our CAPIF/MEC application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Create a new user\n", + " - Get the information to use CAPIF (security materials & URLs)\n", + " - Onboard the provider\n", + " - Print certificates for each function\n", + " - Delete the newly created user\n", + " - Logout\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_publish_url, ccf_discover_url, ccf_security_url, ccf_onboarding_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", + " return\n", + "\n", + " # Get auth & URLs\n", + " auth = None\n", + " user_name = \"\"\n", + " user_uuid = \"\"\n", + " if USE_OCF_SANDBOX:\n", + " auth = get_auth(REGISTER_USER, REGISTER_PASSWORD)\n", + " else:\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " return\n", + "\n", + " # Set the CAPIF access information\n", + " access_token = auth['access_token']\n", + " ca_root = auth['ca_root']\n", + " ccf_api_onboarding_url = auth['ccf_api_onboarding_url']\n", + " ccf_discover_url = auth['ccf_discover_url']\n", + " ccf_onboarding_url = auth['ccf_onboarding_url']\n", + " ccf_publish_url = auth['ccf_publish_url']\n", + " ccf_security_url = auth['ccf_security_url']\n", + " logger.debug(\"ccf_api_onboarding_url:\" + ccf_api_onboarding_url)\n", + " logger.debug(\"ccf_discover_url:\" + ccf_discover_url)\n", + " logger.debug(\"ccf_publish_url:\" + ccf_publish_url)\n", + " logger.debug(\"ccf_security_url:\" + ccf_security_url)\n", + "\n", + " # Onboard the provider\n", + " prov = onboard_provider(\"MECSandbox_to_CAPIF_Provider\", access_token)\n", + " if len(prov) == 0:\n", + " return\n", + "\n", + " # Print certificates for each function\n", + " logger.debug(\"API Provider Id:\" + prov['apiProvDomId'])\n", + " logger.debug(\"AMF: \" + prov['apiProvFuncs'][0]['regInfo']['apiProvCert'])\n", + " logger.debug(\"AEF: \" + prov['apiProvFuncs'][1]['regInfo']['apiProvCert'])\n", + " logger.debug(\"APF: \" + prov['apiProvFuncs'][2]['regInfo']['apiProvCert'])\n", + " logger.debug(\"csr: \" + str(prov['csr']))\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Offboard the API profider\n", + " certs_bundle = (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]) # Use AMF certificate and AMF private key\n", + " file_bundle = offboard_provider(prov['apiProvDomId'], certs_bundle)\n", + " if len(file_bundle) == 0:\n", + " for file in file_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the newly created user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0wHI1ooMbCy3" + }, + "source": [ + "## Using ETSI MEC profile for CAPIF\n", + "\n", + "The purpose is to export the MEC Profile for CAPIF API into our CAPIF application. To achieve it, we need to fulfill the following requirements:\n", + "1. Create an instance of a MEC Sandbox using the '4g-5g-macri-v2x' network scenario\n", + "2. Set TRY_MEC_URL, TRY_MEC_SESSION_ID, TRY_MEC_PLTF constants accordingly\n", + "3. Build the ServiceAPIDescription as described in ETSI TS 129 222 V18.8.0 (2025-07) Table 8.2.4.2.2-1: Definition of type ServiceAPIDescription. This is the role of the function below" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "S7InJDD1_g-v" + }, + "outputs": [], + "source": [ + "def build_publish_api_from_mec_services(p_aefId: str) -> dict:\n", + " \"\"\"\n", + " This function builds the Publish API request body data structure which will be used todo the request for publish API\n", + " :param p_aefId: The AEF ID\n", + " :return The request body data structure on success, an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, TRY_MEC_URL, TRY_MEC_SESSION_ID, TRY_MEC_PLTF\n", + "\n", + " logger.debug('>>> build_publish_api_from_mec_services: p_aefId=' + p_aefId)\n", + "\n", + " # Sanity checks\n", + " if len(p_aefId) == 0:\n", + " logger.error('build_publish_api_from_mec_services: p_aefId is empty')\n", + " return dict()\n", + "\n", + " # Build the service-apis data structure\n", + " publish_api_req_body = {\n", + " \"apiName\": \"MEC Profile for CAPIF\",\n", + " \"aefProfiles\": [\n", + " {\n", + " \"aefId\": p_aefId,\n", + " \"versions\": [\n", + " {\n", + " \"apiVersion\": \"v1\",\n", + " \"expiry\": \"2028-11-30T10:32:02.004Z\",\n", + " \"resources\": [\n", + " {\n", + " \"resourceName\": \"MEC Profile of CAPIF\",\n", + " \"commType\": \"REQUEST_RESPONSE\",\n", + " \"uri\": f\"/{TRY_MEC_SESSION_ID}/{TRY_MEC_PLTF}/service-apis/v1/allServiceAPIs\",\n", + " \"custOpName\": \"string\",\n", + " \"operations\": [\n", + " \"GET\"\n", + " ],\n", + " \"description\": \"Endpoint to access MEC services\"\n", + " }\n", + " ],\n", + " \"custOperations\": [\n", + " {\n", + " \"commType\": \"REQUEST_RESPONSE\",\n", + " \"custOpName\": \"string\",\n", + " \"operations\": [\n", + " \"GET\"\n", + " ],\n", + " \"description\": \"string\"\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"protocol\": \"HTTP_1_1\",\n", + " \"dataFormat\": \"JSON\",\n", + " \"securityMethods\": [\"OAUTH\"],\n", + " \"interfaceDescriptions\": [\n", + " {\n", + " \"ipv4Addr\": TRY_MEC_URL,\n", + " \"port\": TRY_MEC_PORT,\n", + " \"securityMethods\": [\"OAUTH\"]\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"description\": \"MEC Profile of CAPIF\",\n", + " \"supportedFeatures\": \"020\",\n", + " \"shareableInfo\": {\n", + " \"isShareable\": True,\n", + " \"capifProvDoms\": [\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"serviceAPICategory\": \"string\",\n", + " \"apiSuppFeats\": \"fffff\",\n", + " \"pubApiPath\": {\n", + " \"ccfIds\": [\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"ccfId\": \"string\",\n", + " \"apiStatus\":{\n", + " \"aefIds\": [\n", + " p_aefId\n", + " ]\n", + " }\n", + " }\n", + "\n", + " logger.debug('<<< build_publish_api_from_mec_services: ' + str(publish_api_req_body))\n", + " return publish_api_req_body\n", + " # End of build_publish_api_from_mec_services function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PRAie110_r8P" + }, + "source": [ + "Having built the ServiceAPIDescription data structure, the next step is to implement the CAPIF publish API.\n", + "\n", + "To proceed, we need to enable the TLS mutual authentication using the security material obtained during the onboarding APIs operation ([Onboarding APIs](#onboarding_apis)), i.e. the AEF certificate and the AEF private key ([Generate certificates](#Generate_certificates)).\n", + "\n", + "\n", + "**Refer to:** ETSI TS 129 222 V18.8.0 (2025-07) Clauses 5.3 CAPIF_Publish_Service_API and 8.2 CAPIF_Publish_Service_API\n", + "\n", + "Before to proceed with the steps above, let's create 2 helper functions to simpily the implemantation of the CAPIF publish API. These helper functions cover the following operations:\n", + "- Onboarding operations\n", + "- Offboarding operations" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IreHiSXs2U65" + }, + "source": [ + "#### Onboarding operations\n", + "\n", + "The Onboarding operations include th following steps:\n", + "- login\n", + "- create a new user\n", + "- Get the information to use CAPIF (security materials & URLs)\n", + "- onboard the provider\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "nu-tEA6n2TpI" + }, + "outputs": [], + "source": [ + "def onboarding_provider() -> dict:\n", + " \"\"\"\n", + " To onboard the provider using CAPIF endpoint. It includes:\n", + " - login\n", + " - create a new user\n", + " - Get the information to use CAPIF (security materials & URLs)\n", + " - onboard the provider\n", + " :return: A dictionary containing security material and additional context information on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_publish_url, ccf_discover_url, ccf_security_url, ccf_onboarding_url\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", + " return\n", + "\n", + " # Get auth & URLs\n", + " auth = None\n", + " user_name = \"\"\n", + " user_uuid = \"\"\n", + " if USE_OCF_SANDBOX:\n", + " auth = get_auth(REGISTER_USER, REGISTER_PASSWORD)\n", + " else:\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " return dict()\n", + "\n", + " # Set the CAPIF access information\n", + " access_token = auth['access_token']\n", + " ca_root = auth['ca_root']\n", + " ccf_api_onboarding_url = auth['ccf_api_onboarding_url']\n", + " ccf_discover_url = auth['ccf_discover_url']\n", + " ccf_onboarding_url = auth['ccf_onboarding_url']\n", + " ccf_publish_url = auth['ccf_publish_url']\n", + " ccf_security_url = auth['ccf_security_url']\n", + "\n", + " # Onboard the provider\n", + " prov = onboard_provider(\"MECSandbox_to_CAPIF_Provider\", access_token)\n", + " if len(prov) == 0:\n", + " return dict()\n", + "\n", + " # Add context data\n", + " prov['refresh_token'] = refresh_token\n", + " prov['admin_token'] = admin_token\n", + " prov['user_uuid'] = user_uuid\n", + " prov['access_token'] = access_token\n", + "\n", + " return prov\n", + " # End of onboarding_provider function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e940bUcf2deu" + }, + "source": [ + "#### Offboarding operations\n", + "\n", + "The Offboarding operations include th following steps:\n", + "- Offboard the API provide\n", + "- Delete the user\n", + "- Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hEnFLfPI2hms" + }, + "outputs": [], + "source": [ + "def offboarding_provider(p_user_uuid: str, p_api_provider_id: str, p_bundle: tuple, p_admin_token: str) -> int:\n", + " \"\"\"\n", + " To offboard the provider. It includes:\n", + " - Offboard the API provider\n", + " - Delete the user\n", + " - Logout\n", + " :return: 0 on success, or -1 otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, access_token\n", + "\n", + " logger.debug('>>> offboarding_provider: ' + p_user_uuid)\n", + "\n", + " # Offboard the API profider\n", + " file_bundle = offboard_provider(p_api_provider_id, p_bundle)\n", + " if len(file_bundle) == 0: # Remove cert files if any\n", + " for file in file_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the newly created user\n", + " delete_user(p_user_uuid, p_admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " return 0\n", + " # End of offboarding_provider function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9TSYztWMcaOA" + }, + "source": [ + "#### Publish CAPIF API function\n", + "\n", + "As mentionned above , the prupose of this function is to publish an API, using the TLS mutual authentication. To do so, we need the APF certificate (public keys) and its private key for the signature and the encription processes, and the CA certificate for the verification of the peer certifcate.\n", + "\n", + "**Note**: The http.request function required taht the cerficates and the keys are stored on files, not in memory. So, when our CAPIF applicate terminates, these files shall be removed (freeing resource step). This is the reason the publish_capif_api() function return a tuple containing the files created for the TLS mutual authentication operation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "z_Cwazjl_xGJ" + }, + "outputs": [], + "source": [ + "def publish_capif_api(p_apfId: str, p_body: dict, p_bundle: tuple) -> list:\n", + " \"\"\"\n", + " This function is to publish an API on CAPIF server\n", + " :param p_apfId: The APF identifier\n", + " :param p_body: The request body\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " :return: A list containing the files created for the TLS mutual authentication operations on success, or an empty list otherwise\n", + " \"\"\"\n", + " global logger, ccf_publish_url, ca_root\n", + "\n", + " logger.debug('>>> publish_capif_api')\n", + "\n", + " # Sanity checks\n", + " if len(p_bundle) != 2:\n", + " logger.error('publish_capif_api: p_bundle is malformed')\n", + " return []\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_publish_url.replace('', p_apfId)\n", + " logger.debug('publish_capif_api: url=' + url)\n", + " headers = {'Content-Type': 'application/json'}\n", + " logger.debug('publish_capif_api (step1): headers: ' + str(headers))\n", + " logger.debug('publish_capif_api (step2): body: ' + str(p_body))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return []\n", + " logger.debug('publish_capif_api (step3): bundle: ' + str(bundle))\n", + " response = requests.post(url, headers=headers, data=json.dumps(p_body), cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('publish_capif_api (step4): result: ' + str(response.json()))\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return []\n", + " res = json.loads(response.text)\n", + " logger.debug('publish_capif_api (step5): res: ' + str(res))\n", + " api_id = res['apiId']\n", + " return bundle\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"publish_capif_api failed: {e}\")\n", + "\n", + " return []\n", + " # End of function publish_capif_api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-TzvBVLM1fIc" + }, + "source": [ + "### Putting everything together\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "CRtfJ6cm3V6b" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fourth sprint of our CAPIF/MEC application:\n", + " - Onboarding operations\n", + " - Offboarding operations\n", + " \"\"\"\n", + " global logger, ca_root\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " prov = onboarding_provider()\n", + " if len(prov) == 0:\n", + " return\n", + " user_uuid = prov['user_uuid']\n", + "\n", + " # Build the publish_api body request\n", + " aefId = prov['apiProvFuncs'][1]['apiProvFuncId']\n", + " apfId = prov['apiProvFuncs'][2]['apiProvFuncId']\n", + " publish_api_req_body = build_publish_api_from_mec_services(aefId)\n", + " if len(publish_api_req_body) == 0:\n", + " offboarding_provider(user_uuid, prov['apiProvDomId'], (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]), prov['admin_token']) # Use AMF certificate and AMF private key\n", + " return\n", + " logger.debug(\"publish_api_req_body: \" + str(publish_api_req_body))\n", + " certs_bundle = (prov['apiProvFuncs'][2]['regInfo']['apiProvCert'], prov['csr']['apf'][1]) # Use APF certificate and APF private key\n", + "\n", + " # Publish the APIs\n", + " #ipdb.set_trace()\n", + " files_bundle = publish_capif_api(apfId, publish_api_req_body, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " offboarding_provider(user_uuid, prov['apiProvDomId'], (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]), prov['admin_token']) # Use AMF certificate and AMF private key\n", + " return\n", + "\n", + " # Terminate the application\n", + " offboarding_provider(\n", + " user_uuid,\n", + " prov['apiProvDomId'],\n", + " (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]), # Use AMF certificate and AMF private key\n", + " prov['admin_token']\n", + " )\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aABBc4Hizy88" + }, + "source": [ + "## Build an helper function to publish the ETSI MEC profile for CAPIF API\n", + "\n", + "To simply the API invoker process, let's create two helpers functions:\n", + "- One to publish the ETSI MEC profile for CAPIF API\n", + "- One to remove the previously published API" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "r-gZe6mQ4yHH" + }, + "source": [ + "### Helper to publish API" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ozCMG8jh0UMd" + }, + "outputs": [], + "source": [ + "def publish_api() -> tuple:\n", + " \"\"\"\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> publish_api')\n", + "\n", + " prov = onboarding_provider()\n", + " if len(prov) == 0:\n", + " return ()\n", + " amf_cert_bundle = (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]) # Use AMF certificate and AMF private key\n", + "\n", + " # Build the publish_api body request\n", + " aefId = prov['apiProvFuncs'][1]['apiProvFuncId']\n", + " apfId = prov['apiProvFuncs'][2]['apiProvFuncId']\n", + " apiId = prov['apiProvDomId']\n", + " publish_api_req_body = build_publish_api_from_mec_services(aefId)\n", + " if len(publish_api_req_body) == 0:\n", + " offboarding_provider(prov['user_uuid'], apiId, amf_cert_bundle, prov['admin_token'])\n", + " return ()\n", + " logger.debug(\"publish_api_req_body: \" + str(publish_api_req_body))\n", + " certs_bundle = (prov['apiProvFuncs'][2]['regInfo']['apiProvCert'], prov['csr']['apf'][1]) # Use APF certificate and APF private key\n", + "\n", + " # Publish the APIs\n", + " files_bundle = publish_capif_api(apfId, publish_api_req_body, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " offboarding_provider(prov['user_uuid'], apiId, amf_cert_bundle, prov['admin_token']) # Use AMF certificate and AMF private key\n", + " return ()\n", + "\n", + " logger.debug('publish_api: ' + str((apiId, amf_cert_bundle)))\n", + " return (apiId, amf_cert_bundle, prov['user_uuid'], prov['admin_token'])\n", + " # End of function publish_api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lrnfAlrZ1-TB" + }, + "source": [ + "### Helper to remove published API" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Ql9dC3P41_nO" + }, + "outputs": [], + "source": [ + "def remove_publish_api(p_user_uuid: str, p_api_id: str, p_bundle: tuple, p_admin_token: str):\n", + " \"\"\"\n", + " To remove published API.\n", + " :param p_user_uuid: The user identifier\n", + " :param p_api_id: The API identifier\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> remove_publish_api ')\n", + "\n", + " # Terminate the application\n", + " offboarding_provider(p_user_uuid, p_api_id, p_bundle, p_admin_token)\n", + "\n", + " return\n", + " # End of function reove_publish_api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UNN73-Zg4WZ-" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Let's test these two helpers functions before to go ahead" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bVYS13iV4-s8" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " To test both helpers functions:\n", + " - publish_api\n", + " - remove_publish_api\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Publish the MEC profile for CAPIF API\n", + " res = publish_api()\n", + " if len(res) == 0:\n", + " return\n", + "\n", + " api_id, bundle, user_uuid, admin_token = res\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Remove the MEC profile for CAPIF API\n", + " remove_publish_api(user_uuid, api_id, bundle, admin_token)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9W2SvVdx6fxk" + }, + "source": [ + "## Using the published API: The CAPIF Invoker side\n", + "\n", + "Now that we are able to publish an API on the CAPIF server, the next step is to use it in order to invoke some MEC Services API. To achieve this goal, we have to implement the CAPI Invoker, following these steps:\n", + "- Onboard an API invoker\n", + "- Discover the published APIs\n", + "- Get a MEC Service API (this step requires that a MEC Sandox platform is already running)\n", + "- Offboard an API invoker" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YasCvixW7E4o" + }, + "source": [ + "### Onboard an API invoker\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "f11_uMS67I9J" + }, + "outputs": [], + "source": [ + "def onboard_invoker(p_name: str, p_access_token: str) -> dict:\n", + " \"\"\"\n", + " To onboard the API invoker.\n", + " :param p_name: The name of the invoker\n", + " :return: A dictionary containing security material for each CAPIF endpoint on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, ccf_onboarding_url\n", + "\n", + " logger.debug('>>> onboard_invoker: ' + p_name)\n", + " logger.debug('>>> onboard_invoker: ' + p_access_token)\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_onboarding_url\n", + " logger.debug('onboard_invoker: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_access_token}\n", + " logger.debug('onboard_invoker (step1): headers: ' + str(headers))\n", + " # Request body for onboarding the invoker\n", + " invoker_csr_request, invoker_private_key = generate_csr(\"API Invoker\", \"ETSI\", \"Fr\")\n", + " data = {\n", + " \"notificationDestination\" : \"http://host.docker.internal:8086/netapp_callback\",\n", + " \"supportedFeatures\" : \"fffffff\",\n", + " \"apiInvokerInformation\" : \"dummy\",\n", + " \"websockNotifConfig\" : {\n", + " \"requestWebsocketUri\" : True,\n", + " \"websocketUri\" : \"websocketUri\"\n", + " },\n", + " \"onboardingInformation\" : {\n", + " \"apiInvokerPublicKey\" : invoker_csr_request.decode(\"utf-8\"),\n", + " },\n", + " \"requestTestNotification\" : True\n", + " }\n", + " logger.debug('onboard_invoker (step2): body: ' + str(data))\n", + " response = requests.post(url, headers=headers, data=json.dumps(data), verify=\"ca_root.pem\")\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('onboard_invoker (step3): result: ' + str(response.json()))\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " # Add an entry for CSRs and private keys for future usage\n", + " res['csr'] = [invoker_csr_request, invoker_private_key]\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"onboard_invoker failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function onboard_invoker" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kQmJW-d99cGo" + }, + "source": [ + "### Offboard an API invoker" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "KRC_xkGO9hEY" + }, + "outputs": [], + "source": [ + "def offboard_invoker(p_invoker_id: str, p_bundle: tuple) -> list:\n", + " \"\"\"\n", + " To offboard the API invoker.\n", + " :param p_invoker_id: The API invoker identifier\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, ccf_onboarding_url, ca_root\n", + "\n", + " logger.debug('>>> offboard_invoker: ' + p_invoker_id)\n", + "\n", + " try:\n", + " # Delete the newly created user\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_onboarding_url + '/' + p_invoker_id\n", + " logger.debug('offboard_invoker: url=' + url)\n", + " headers = {'Content-Type': 'application/json'}\n", + " logger.debug('offboard_invoker (step1): headers: ' + str(headers))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return []\n", + " logger.debug('offboard_invoker (step2): bundle: ' + str(bundle))\n", + " response = requests.delete(url, headers=headers, cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " logger.debug('offboard_invoker (step3): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 204:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return []\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"offboard_invoker failed: {e}\")\n", + " return []\n", + "\n", + " return bundle\n", + " # End of function offboard_invoker" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-h0zz7ocxtyv" + }, + "source": [ + "### Discover published APIs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ofUuploUxuhn" + }, + "outputs": [], + "source": [ + "def discover(p_invoker_id: str, p_bundle: tuple, p_access_token: str) -> dict:\n", + " \"\"\"\n", + " To discover the APIs published by capif core.\n", + " :param p_invoker_id: The API invoker identifier\n", + " :return: A dictionary containing the APIs published by capif core on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ca_root, ccf_discover_url\n", + "\n", + " logger.debug('>>> Discover APIs published by capif core')\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_discover_url + p_invoker_id\n", + " logger.debug('Discover: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_access_token}\n", + " logger.debug('Discover (step1): headers: ' + str(headers))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return dict()\n", + " logger.debug('Discover (step2): bundle: ' + str(bundle))\n", + " response = requests.get(url, headers=headers, cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " logger.debug('Discover (step3): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('Discover : result: ' + str(response.json()))\n", + " if response.status_code != 200:\n", + " logger.error(f\"Discovery failed: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Discovery failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function discover\n", + "\n", + "def extract_ipv4_and_uri(p_response_data: json.loads) -> dict:\n", + " # Extract ipv4Addr using a list comprehension\n", + " ipv4_addrs = [\n", + " desc.get(\"ipv4Addr\")\n", + " for profile in p_response_data.get(\"serviceAPIDescriptions\", [])\n", + " for aef in profile.get(\"aefProfiles\", [])\n", + " for desc in aef.get(\"interfaceDescriptions\", [])\n", + " ]\n", + "\n", + " # Extract uri using a list comprehension\n", + " uris = [\n", + " resource.get(\"uri\")\n", + " for profile in p_response_data.get(\"serviceAPIDescriptions\", [])\n", + " for aef in profile.get(\"aefProfiles\", [])\n", + " for version in aef.get(\"versions\", [])\n", + " for resource in version.get(\"resources\", [])\n", + " ]\n", + "\n", + " return {\"ipv4Addr\": ipv4_addrs, \"uri\": uris}\n", + " # End of function extract_ipv4_and_uri" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RElS9XFZ9hvQ" + }, + "source": [ + "### Putting everything together" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "QPZPYJZM9mNr" + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fiveth sprint of our CAPIF/MEC application:\n", + " - Publish the MEC profile for CAPIF API\n", + " - Create a new user for the invoker\n", + " - Get certificates\n", + " - Onboad the API invoker\n", + " - Do the discovery\n", + " - Offboard the API invoker\n", + " - Delete the\n", + " - Logout the invoker user\n", + " - Remove the MEC profile for CAPIF API\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_discover_url, ccf_onboarding_url, ccf_publish_url, ccf_security_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Publish the MEC profile for CAPIF API\n", + " res = publish_api()\n", + " if len(res) == 0:\n", + " return\n", + " api_id, bundle, prov_user_uuid, prov_admin_token = res\n", + "\n", + " # Login for the new user for the invoker\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Create a new user for the invoker\n", + " res = create_user(admin_token)\n", + " if len(res) == 0:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + " user_name, user_uuid = res\n", + "\n", + " # Get certificates\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " delete_user(user_name, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Sanity checks\n", + " if auth['ca_root'] != ca_root:\n", + " raise Exception('CA root mismatch')\n", + " if auth['ccf_api_onboarding_url'] != ccf_api_onboarding_url:\n", + " raise Exception('CCF API onboarding URL mismatch')\n", + " if auth['ccf_discover_url'] != ccf_discover_url:\n", + " raise Exception('CCF discover URL mismatch')\n", + " if auth['ccf_onboarding_url'] != ccf_onboarding_url:\n", + " raise Exception('CCF onboarding URL mismatch')\n", + " if auth['ccf_publish_url'] != ccf_publish_url:\n", + " raise Exception('CCF publish URL mismatch')\n", + " if auth['ccf_security_url'] != ccf_security_url:\n", + " raise Exception('CCF security URL mismatch')\n", + " access_token = auth['access_token']\n", + "\n", + " # Onboad the API invoker\n", + " res = onboard_invoker('API Invoker', access_token)\n", + " if len(res) == 0:\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Do the discovery\n", + " invoker_id = res['apiInvokerId']\n", + " certs_bundle = (res['onboardingInformation']['apiInvokerCertificate'], res['csr'][1])\n", + " mec_api = discover(invoker_id, certs_bundle, access_token)\n", + " if len(mec_api) == 0:\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Extract the URL to access to the MEC Sandbox platform\n", + " addrs = extract_ipv4_and_uri(mec_api)\n", + " logger.debug('addrs: ' + str(addrs))\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Offboard the API invoker\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the invoker user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout the invoker user\n", + " process_logout()\n", + "\n", + " # Remove the MEC profile for CAPIF API\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6tJWDz4woyz1" + }, + "source": [ + "## Discoverig MEC services\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Uy-XNKA9pN5h" + }, + "source": [ + "### Invoking the MEC profile for CAPIF\n", + "\n", + "After discovering the published API, we have the information (see content of addrs data structure in previous execution) to do a request to an existing MEC Sandbox platform to get the list of the MEC services exposed (see TRY_MEC_SESSION_ID)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ZL8Gyo0Ao2_u" + }, + "outputs": [], + "source": [ + "def discovering_mec_services(p_url: str) -> dict:\n", + " \"\"\"\n", + " To discover MEC services API\n", + " :param p_url: The URL to access to the MEC Sandbox platform\n", + " :return: A dictionary containing the MEC services on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> discovering_mec_services: ' + p_url)\n", + "\n", + " try:\n", + " headers = {'Content-Type': 'application/json', 'accept': 'application/json',}\n", + " logger.debug('discovering_mec_services (step1): headers: ' + str(headers))\n", + " response = requests.get(p_url, headers=headers)\n", + " logger.debug('discovering_mec_services (step2): result: ' + str(response.json()))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 200:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"discovering_mec_services failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function discovering_mec_services" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wa_8khiGpTAa" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Here is the last and complete version of our CAPIF application achieving the main objective of this tutorial: __Retrieve the MEC services exposed by an existing MEC Sandbox platform.__\n", + "\n", + "The process involves following steps:\n", + "- Create new User (API Provider)\n", + "- Onboard API Provider\n", + "- Publish MEC APIs on CCF\n", + "- Create new User (API Invoker)\n", + "- Onboard API Invoker\n", + "- Discovery of APIs by API Invoker\n", + "- Request Discovered API (Get MEC Services)\n", + "- Offboard API Invoker/API Provider\n", + "- Delete Users" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "aTllbmoUpXKx", + "scrolled": true + }, + "outputs": [], + "source": [ + "#%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fiveth sprint of our CAPIF/MEC application:\n", + " - Publish the MEC profile for CAPIF API\n", + " - Create a new user for the invoker\n", + " - Get certificates\n", + " - Onboad the API invoker\n", + " - Do the discovery\n", + " - Offboard the API invoker\n", + " - Delete the\n", + " - Logout the invoker user\n", + " - Remove the MEC profile for CAPIF API\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_discover_url, ccf_onboarding_url, ccf_publish_url, ccf_security_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Publish the MEC profile for CAPIF API\n", + " res = publish_api()\n", + " if len(res) == 0:\n", + " return\n", + " api_id, bundle, prov_user_uuid, prov_admin_token = res\n", + "\n", + " # Login for the new user for the invoker\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Create a new user for the invoker\n", + " res = create_user(admin_token)\n", + " if len(res) == 0:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + " user_name, user_uuid = res\n", + "\n", + " # Get certificates\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " delete_user(user_name, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Sanity checks\n", + " if auth['ca_root'] != ca_root:\n", + " raise Exception('CA root mismatch')\n", + " if auth['ccf_api_onboarding_url'] != ccf_api_onboarding_url:\n", + " raise Exception('CCF API onboarding URL mismatch')\n", + " if auth['ccf_discover_url'] != ccf_discover_url:\n", + " raise Exception('CCF discover URL mismatch')\n", + " if auth['ccf_onboarding_url'] != ccf_onboarding_url:\n", + " raise Exception('CCF onboarding URL mismatch')\n", + " if auth['ccf_publish_url'] != ccf_publish_url:\n", + " raise Exception('CCF publish URL mismatch')\n", + " if auth['ccf_security_url'] != ccf_security_url:\n", + " raise Exception('CCF security URL mismatch')\n", + " access_token = auth['access_token']\n", + "\n", + " # Onboad the API invoker\n", + " res = onboard_invoker('API Invoker', access_token)\n", + " if len(res) == 0:\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Do the discovery\n", + " invoker_id = res['apiInvokerId']\n", + " certs_bundle = (res['onboardingInformation']['apiInvokerCertificate'], res['csr'][1])\n", + " mec_api = discover(invoker_id, certs_bundle, access_token)\n", + " if len(mec_api) == 0:\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Extract the URL to access to the MEC Sandbox platform\n", + " addrs = extract_ipv4_and_uri(mec_api)\n", + " logger.debug('addrs: ' + str(addrs))\n", + "\n", + " # Discovering MEC services\n", + " url = ''\n", + " if 'ports' in addrs:\n", + " url = 'https://' + addrs['ipv4Addr'][0] + ':' + addrs['ports'][0]\n", + " else:\n", + " url = 'https://' + addrs['ipv4Addr'][0]\n", + " url += addrs['uri'][0]\n", + " mec_services = discovering_mec_services(url)\n", + " if len(mec_services) != 0:\n", + " logger.debug('===> The list of the MEC services exposed by ' + addrs['ipv4Addr'][0] + ' is: ' + str(mec_services))\n", + "\n", + " # Offboard the API invoker\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the invoker user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout the invoker user\n", + " process_logout()\n", + "\n", + " # Remove the MEC profile for CAPIF API\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cfy6D8wYt5GA" + }, + "source": [ + "### What to do next\n", + "\n", + "here is a list of several additional tasks yo can do to refine our CAPIF application:\n", + "1. Simply the code of the process_main() function above\n", + "2. Find the endpoint of the MEC Location API service (MEC 013) and send a request to get the list of zones available\n", + "3. Create your own CAPIF application" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m_BbV7KdpX24" + }, + "source": [ + "## Conlusion: what do we learn?\n", + "\n", + "The main objective of this tutorial is to demonstrate how to use the MEC profile for CAPIF (as described in MEC 011 v3.x.x Clause 9) in a CAPIF application. Along this tutrial, we learned how to develop a basic CAPIF application, including both API provider and API invoker. We learned also how to use a published API to send REQUEST to a MEC Sandbox platform instance.\n", + "\n", + "\n", + "**That's all Folks**\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "colab": { + "private_outputs": true, + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/demo6/python/notebook/.ipynb_checkpoints/ETSIMEC_TechTalk_tutorial-checkpoint.ipynb b/examples/demo6/python/notebook/.ipynb_checkpoints/ETSIMEC_TechTalk_tutorial-checkpoint.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..fedbc12411467bf6050b78d74d163c47f38ebb96 --- /dev/null +++ b/examples/demo6/python/notebook/.ipynb_checkpoints/ETSIMEC_TechTalk_tutorial-checkpoint.ipynb @@ -0,0 +1,820 @@ +{ + "cells": [ + { + "attachments": { + "image-3.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![image-3.png](attachment:image-3.png)\n", + "\n", + "# MEC Tech Talk \\#15\n", + "\n", + "# Tutorial: Exploring Edge Native Apps\n", + "\n", + "\n", + "## Objectives:\n", + "+ Understanding Edge Native Apps\n", + "+ Explore both queries and subscriptions / notifications." + ] + }, + { + "attachments": { + "image-4.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "## (1) Initialize the MEC Sandbox\n", + "\n", + "MEC Sandbox is an interactive environment that enables users to learn & experiment with ETSI MEC Service APIs.\n", + "\n", + "\n", + "| ![image-4.png](attachment:image-4.png) | First, open a browser and go to the ETSI MEC Sandbox https://try-mec.etsi.org/ (Use your github access credentials to login.) |\n", + "| --------------|-------------- |\n", + "\n", + "\n", + "### We will perform the following steps on the Sandbox:\n", + "\n", + "1. Launch the Sandbox enviroment by selecting a network to be deployed.\n", + "2. Choose a MEC API in the MEC Service APIs menu.\n", + "3. Create a new MEC App\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "appInstanceId = \"\" # <-- add here your aplication instance ID \n", + "mep_id = \"\" # <-- add here MEP (MEC Platform) to be used" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The MEC Service base path\n", + "\n", + "The service base path can be used in your MEC application to interact directly with the MEC Service API in your MEC Sandbox. It is composed by the **Sandbox URL**, the sandbox user **Authentication Token**, the **MEP ID** and the **MEC API** and **Version**. \n", + "![image.png](attachment:image.png)" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You will find the \"MEC service base path\" box in the \"Try-it from your User Application\" section of the the [MEC Sandbox](https://try-mec.etsi.org/).\n", + "\n", + "Find the ```AUTH_TOKEN``` in the \"MEC service base path\" and copy it:\n", + "\n", + "![image.png](attachment:image.png)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "auth_token = \"\"\n", + "\n", + "mec_base_path = \"https://try-mec.etsi.org/\" + auth_token + \"/\" + mep_id " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import libraries\n", + "We will import:\n", + "- the ```requests``` library that will allow us to make HTTP/1.1 requests to a specified URL and query MEC the REST APIs \n", + "- the ```json``` module, a standard-library for data interchange between JSON and Python data types.\n", + "- the ```pandas``` library, for data analysis and manipulation in Phyton.\n", + "- the ```re``` library, for using regular expressions (RegEx) in Phyton." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import requests, json\n", + "import pandas as pd\n", + "import re" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "## (2) Initialising the MEC App using the MEC Application Support API (MEC011)\n", + "\n", + "For this part of the tutorial, we will use the MEC Application Support API ```mec_app_support```, version 2." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mec011_app_support_path = mec_base_path + \"/mec_app_support/v2\"" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB5oAAACLCAYAAACwTpWqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAHEvSURBVHhe7f1/VFTnvff/P3HEKCSCrehdippyNGq9sY3kpGM/Qc4nJAdtDjRVTyJZnhBNjUZtpJ9UThNcVfMVvW+4s0JSNRBOJKSuYFI1KRwrnpSsCq46zQnayknVW0oiEnoEW8Fm0DKOfv+YPcNmMwMzCIrm9Vhrlrj3NcM11772NcP1vn6EdXZ2XmWAXb3a9ZLmn0VEREREREREREREREREZGgICwvz+3MwwgYy0OwNKl+9etX3MB8XEREREREREREREREREZEbzxtYDgsL8z3Mx/syYIFmc3D5ypUrXLlypdsxBZtFRERERERERERERERERG48c3A5LCyMYcOGMWzYsB5B594MSKDZHGB2u92+hzXgLCIiIiIiIiIiIiIiIiIiN5Y1wGyz2XwPc8C5N9ccaLYGmS9fvkz9xVYO/PUEdR3NXHBfsj5FRERERERERERERERERERusNG2kSRExJJ6xzQmj4ph+PDhQQebByTQfOXKFS5fvszly5f59V9P8ca5D1kQM4u/j/oaY4aPsj5FRERERERERERERERERERusPOXL/Kf7Z+wp/UIT4y9l3+4YwrDhw9n+PDhvmBzINcUaPbOZna73bhcLk51tLDpv/+D5++cx6SRX7YmFxERERERERERERERERGRIeb0pT+z+dP9rPsf/8iUiHGEh4djs9l6ndU8zHogVOZlsw/89QQLYmYpyCwiIiIiIiIiIiIiIiIicpOYNPLLLIiZxYG/nsDtdnPlyhWuXu19vvKABJq9wea6jmb+Pupr1iQiIiIiIiIiIiIiIiIiIjKE/X3U16jraPYFmQct0Gx+cW+g+YL7kvZkFhERERERERERERERERG5yYwZPooL7kvdZjP3FnDud6DZy/vigX6BiIiIiIiIiIiIiIiIiIjcHIKN/4Z1dnb2niIA74u73W46Ozu5dOkSSz97m5KvP2FN6nPm0nnKz/2Ous8/429XLltPyw1027DhJNz+VdLHfpMJI8dYT4uIiIiIiIiIiIiIiIjILW7JH95gx1cfZeTIkYwYMQKbzUZYWBhhYWHWpNc+ozlYZy6dZ8unv+SjC6cV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QsKEiK42akQEcTPmkLkul1Wm+zA0Ecx6djM5j0xmnHFdw0dPJGlZDq9k38Pt1uQh60e9D6HNIXI6y7blkbO4+zX1pn2zoCvYJkGyTWfZ9myW3TeRyF5XNIkgYVUeb25ZRNKUqK421hZO9JSZLHwmh5wFgYPJN5VRM1lZsJq5U7z3n/dezaNwlbFeZwDBtDkhU70XERERERERERHpl1t7RrP0W6Dr2DcXjo1Pkf8hMD6N/B3zB3jfpMCzpvwLNb3cNBr28vQPKmgB7M++xtr7gw86+Ga7jU0lv3TRgNTRgDPcAgg1vXwxtFXk8mRhfeAZzddQ70VEZAAFOQNRM5pFvsg0o1lERERERG5OmtEsN865Dyg39vu0L04bkACeiD91/27s4T0+jQUhBtvGefewPlfDm3sacQVYUljkenKdc/DGW569l5kx2W/7eS31XkRERERERERERERkIGlGs/gV6DreeKHOUA41vXwhuJspz8qh1Ly59jXWj1BnKIeaXm5hvn2ZvSJIWV/AynsVSB4q+tpT3aqvPZNFREREREREREREhirNaBYR6Y0tlvQX81ibNrlrb3CRG83Yo3vZls0KMouIiIiIiIiIiIjIkKcZzeJXoOsoIiIiIiIiIiIiIiIiIrcmzWgWEREREREREREREREREZFBo0CziIiIiIiIiIiIiIiIiIiERIFmEREREREREREREREREREJyXULNN82bLj1kAxRulYiIiIiIiIiIiIiIiIi0pvrFmhOuP2r1kMyROlaiYiIiIiIiIiIiIiIiEhvrlugOX3sNxk1LNx6WIaYUcPCSR/7TethERERERERERERERERERGf6xZonjByDM/d+R3uGT1JSzMPQbcNG849oyfx3J3fYcLIMdbTIiIiIiIiIiIiIiIiIiI+YZ2dnVetB4Nx9epVrl69itvtxuVycenSJZY07aLk609Yk4qIiIiIiIiIiIiIiIiIyBC35A9vUBK3iJEjRxIeHo7NZiMsLIywsDBr0muf0ex9YX8vLiIiIiIiIiIiIiIiIiIiN49g47/9DjSbXzwsLIxhw4Yx2jaS85cvWpOKiIiIiIiIiIiIiIiIiMgQdv7yRUbbRjJs2LBuceBAAed+B5q9vC8+bNgwEiJi+c/2T6xJRERERERERERERERERERkCPvP9k9IiIj1BZoDBZi9BiTQPGzYMGw2G6l3TGNP6xFOX/qzNZmIiIiIiIiIiIiIiIiIiAxBpy/9mT2tR0i9Yxo2m63brOZAwjo7O69aD4bi6tWrXLlyhcuXL3P58mV+/ddTvHHuQxbEzOLvo77GmOGjrE8REREREREREREREREREZEb7Pzli/xn+yfsaT3CE2Pv5R/umMLw4cMZPnx4n8HmAQk0e4PNbreby5cvU3+xlQN/PUFdRzMX3JesTxERERERERERERERERERkRtstG0kCRGxpN4xjcmjYhg+fHi3Gc2DGmjGT7DZ+7hy5QpXrlzxnRcRERERERERERERERERkRvLG0QeNmyYb5tk7yOYIDMDFWjGFGz2BpzNAWYFmkVEREREREREREREREREhgZvINkacDYf68uABZoxgs3ef83BZQWZRURERERERERERERERESGDm8w2RpcDibIzEAHmr3MgWUFmUVEREREREREREREREREhh5zUDnYALPXoASazRRoFhEREREREREREREREREZekINLpsNeqBZRERERERERERERERERERuLcOsB0RERERERERERERERERERHqjQLOIiIiIiIiIiIiIiIiIiIREgWYREREREREREREREREREQmJAs0iIiIiIiIiIiIiIiIiIhISBZpFRERERERERERERERERCQkCjSLiIiIiIiIiIiIiIiIiEhIFGgWEREREREREREREREREZGQKNAsIiIiIiIiIiIiIiIiIiIhUaBZRERERERERERERERERERCokCziIiIiIiIiIiIiIiIiIiEJKyzs/Oq9eBAunp1UF9eRERERERERERERERERET6ISwszHooaIMSaDYHlxVoFhEREREREREREREREREZesyB5lCDzgMaaPYGla9evep7mI+LiIiIiIiIiIiIiIiIiMiN5w0sh4WF+R7m430ZsECzObh85coVrly50u2Ygs0iIiIiIiIiIiIiIiIiIjeeObgcFhbGsGHDGDZsWI+gc28GJNBsDjC73W7fwxpwFhERERERERERERERERGRG8saYLbZbL6HOeDcm2sONFuDzJcvX6b+YisH/nqCuo5mLrgvWZ8iIiIiIiIiIiIiIiIiIiI32GjbSBIiYkm9YxqTR8UwfPjwoIPNAxJovnLlCpcvX+by5cv8+q+neOPchyyImcXfR32NMcNHWZ8iIiIiIiIiIiIiIiIiIiI3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zgmJPPa9exg3sp2T5fspP9HebaUOERERERERGTo0o1lunHMfUG7s92lfnDYgATwRf+r+3diHdHwaC0IMto3z7mF9roY39zTiCrCksMj15Drn4I23jCWKZ0z2235eS70XCZXrTDUFzxlLnI+2syxDQWYREQnRzMWe/akx9vzO20XVhRjSQ9zPW0RERERERIYmzWgWvwJdxxsv1BnKoaaXLwR3M+VZOZSaN9e+xvoR6gzlUNPLLcy3L7NXBCnrC1h5rwLJQ0Vfe6pb3dwztPzsSz5iIpkFG0nXHpoiIiIiIiIiIiK3PM1oFhHpjS2W9BfzWJs2uWt/U5Ebzdije9mWzQoyy9Dg2/dZQWYRERERERERERHpSTOaxa9A11FEREREREREREREREREbk2a0SwiIiIiIiIiIiIiIiIiIoNGgWYREREREREREREREREREQmJAs0iIiIiIiIiIiIiIiIiIhKS6xZovm3YcOshGaJ0rURERERERERERERERESkN9ct0Jxw+1eth2SI0rUSERERERERERERERERkd5ct0Bz+thvMmpYuPWwDDGjhoWTPvab1sMiIiIiIiIiIiIiIiIiIj7XLdA8YeQYnrvzO9wzepKWZh6Cbhs2nHtGT+K5O7/DhJFjrKdFRERERERERERERERERHzCOjs7r1oPBuPq1atcvXoVt9uNy+Xi0qVLLGnaRcnXn7AmFRERERERERERERERERGRIW7JH96gJG4RI0eOJDw8HJvNRlhYGGFhYdak1z6j2fvC/l5cRERERERERERERERERERuHsHGf/sdaDa/eFhYGMOGDWO0bSTnL1+0JhURERERERERERERERERkSHs/OWLjLaNZNiwYd3iwIECzv0ONHt5X3zYsGEkRMTyn+2fWJOIiIiIiIiIiIiIiIiIiMgQ9p/tn5AQEesLNAcKMHsNSKB52LBh2Gw2Uu+Yxp7WI5y+9GdrMhERERERERERERERERERGYJOX/oze1qPkHrHNGw2W7dZzYGEdXZ2XrUeDMXVq1e5cuUKly9f5vLly/z6r6d449yHLIiZxd9HfY0xw0dZnyIiIiIiIiIiIiIiIiIiIjfY+csX+c/2T9jTeoQnxt7LP9wxheHDhzN8+PA+g80DEmj2BpvdbjeXL1+m/mIrB/56grqOZi64L1mfIiIiIiIiIiIiIiIiIiIiN9ho20gSImJJvWMak0fFMHz48G4zmgc10IyfYLP3ceXKFa5cueI7LyIiIiIiIiIiIiIiIiIiN5Y3iDxs2DDfNsneRzBBZgYq0Iwp2OwNOJsDzAo0i4iIiIiIiIiIiIiIiIgMDd5AsjXgbD7WlwELNGMEm73/moPLCjKLiIiIiIiIiIiIiIiIiAwd3mCyNbgcTJCZgQ40e5kDywoyi4iIiIiIiIiIiIiIiIgMPeagcrABZq9BCTSbKdAsIiIiIiIiIiIiIiIiIjL0hBpcNhv0QLOIiIiIiIiIiIiIiIiIiNxahlkPiIiIiIiIiIiIiIiIiIiI9EaBZhERERERERERERERERERCcn/H/HOwgXszBguAAAAAElFTkSuQmCC" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Notify the MEP that this instance is up and running\n", + "- The confirm_ready method is used by the MEC application instance to notify the MEC platform that it is up and running.\n", + "\n", + "\n", + "![image-3.png](attachment:image-3.png)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/confirm_ready\"\n", + "payload_dic = {\"indication\": \"READY\"} # <-- Message content\n", + "method = \"POST\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZMAAAAvCAYAAACWulRZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACESSURBVHhe7d19cFRlni/wb6chsvbooHeq9zppKtCWyNadtUazU4CG5o6yOpt1QkQJ8bY1kp250WWBnQACorAlccC4EBS4jJMazAxFjyFASBg2I17QIgaTlJvBq5SXQQ2k6BR3urwSvbYLCd25fzzn5TlPn3O6O4QQ4Pup6iL0efr0c5637v6d5zzH09/fPwgiIiIiIiIiIiIiIhc56hNERERERERERERERCqP08zkwUHxtP4vEREREREREREREV0fPB6P5V/YBZMHBweNRzKZNP4mIiIiIiIiIiIiomufx+OBx+NBTk6O8bfH47EGk/XAcSKRwMWLF5FIJJBIJJBMJo3tRERERERERERERHTt0Wch5+TkwOv1wuv1YsyYMfB6vanB5GQyiUQigf7+fvR86cHhqA8nv/CiPyHvkoiIiIiIiIiIiIiuVbleYPKtCTwQiCP/24PIzc0VAWU9mKwva9Hf349T5wbxyw9vRtEdgyi4LYkbvOruiIiIiIiIiIiIiOhadCEBdJ3NQcsnHvzjXV9h0i0e5ObmIkdOlEwmcfHiRRyO+lB0xyDuDTCQTERERERERERERHQ9ucEL3BtIouiOQRyO+nDx4kUkk0kzmCzfdO/kF14U3CbWSSYiIiIiIiIiIiKi60/BbSJWnEwmMTg4aJ2ZbCx1kRDRZyIiIiIiIiIiIiK6Pt3gBfoTYkWLlGAytIAyERERERERERERERGkmLFxA75kMomBgQHE43E82+7Hy7MS6msAAE0nPOg6m4ML9ptJcYNXTAcvmcIgPREREREREREREV1dlh/yYv30GHw+X+rMZDdNJzx4L8pAcjYuJID3ojloOuFRNxERERERERERERFdNbIKJnedzSo5SVh2REREREREREREdDXLKsLJGclDx7IjIiIiIiIiIiKiq1lWwWQiIiIiIiIiIiIiuj4xmExEREREREREREREaTGYTERERERERERERERpMZhMRERERERERERERGkxmEw0WiRiaH91OcKPlaKkeDO61O101YvtqkRJcSnmPrEcdR/E1c1EqaKNWFJcipKURyWao2rioehF86JSLNnV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ERERERERERERjRYMJhMRERERERERERFRWlkFk2/gKhdDxrIjIiIiIiIiIiKiq1lWweSC25LqU5Qhlh0RERERERERERFdzbIKJpdMGcS9gSRn2WbhBi9wbyCJkimD6iYiIiIiIiIiIiKiq4anv79/EACSySQGBgYQj8fxbLsfL89KqGmJiIiIiIiIiIiI6Dqy/JAX66fH4PP5Umcmezwe9SkiIiIiIiIiIiIiuk7pMWNLMNnj8SAnJwe5XuACJyYTERERERERERERXbcuJIBcL5CTkyNix/oGj8djBJMn35pA19mUSctEREREREREREREdJ3oOitixSnBZEBEmMeMGYMHAnG0fOLBe9EczlAmIiIiIiIiIiIiuo5cSADvRXPQ8okHDwTiGDNmjAgo6zfgg3YTvkQigf7+fvR86cHhqA8nv/CinwFlIiIiIiIiIiIioutCrheYfGsCDwTiyP/2IHJzc+H1eq3B5MHBQQwODiKRSODixYtIJBJIJBJIJpPGdiIiIiIiIiIiIiK69hg32svJgdfrhdfrxZgxY0Qg2eOxBpMhBZQHBweRTCaNv4mIiIiIiIiIiIjo2iffX0//2+Px4P8D+retECtDVJ8AAAAASUVORK5CYII=" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check Subscriptions\n", + "\n", + "The GET method may be used to request information about all subscriptions for this requestor. Upon success, the response contains message content with all the subscriptions for the requestor.\n", + "\n", + "![image-3.png](attachment:image-3.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecAppSupportApi.yaml#/appSubscriptions/ApplicationsSubscriptions_GET)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {}\n", + "method = \"GET\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + "print(\"Status Code\", response.status_code) \n" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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T2kxrrBwfSoiIiIiIiIiIiIyUCkthoiIiIiIiIiIiIhcMjEHlwc7YltkTALUdyIiIiIiIiIiInK9iDm4/EDKWPsmiZL6TkRERERERERERK4XMQeXZ92excO33KVZuDEY7HDy8C13Mev2LHuRiIiIiIiIiIiIyDUp5gX9REREREREREREROTGEdOCfnFxcfZNIiIiIiIiIiIiInKDChczDgkux8XFER8fjzM+gXNej71YRERERERERERERG4Q57wenPEJxMfHhwSYg4LLcXFx/uDyvUkj+fDUl9ZiEREREREREREREbmBfHjqS+5NGukPLlsDzCEzl+Pj40lISOCRlPFsPPIx247v1wxmERERERERERERkRvIOa+Hbcf3s/HIxzySMp6EBGP2slXQgn6Yi/p5vV56enr48uxRfn9qL388cxhP73lrNRERERERERERERG5TjnjE7g3aSSPpIxn7E3DSExMxOFwBAWYQ4LLFy5c4MKFC3i9Xs6fP4/X68Xr9dLb2+svFxEREREREREREZHrjy/tRXx8PA6HA4fDQUJCAg6HIyQtRkhwGUuA+cKFC/T29vr/LiIiIiIiIiIiIiLXP+v6fL6/2xf0Cxtc9vEFlBVYFhEREREREREREbmx+ILJ9qCyT5/BZRERERERERERERGRcIKX9xMRERERERERERERiYKCyyIiIiIiIiIiIiISMwWXRURERERERERERCRm/x86EXQfaRQDAwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subscribe to AppTermination Notification\n", + "The POST method may be used to create a new subscription. One example use case is to create a new subscription to the MEC service availability notifications. Upon success, the response contains entity body describing the created subscription.\n", + "\n", + "![image.png](attachment:image.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecAppSupportApi.yaml#/appSubscriptions/ApplicationsSubscriptions_POST)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {\n", + " \"subscriptionType\": \"AppTerminationNotificationSubscription\",\n", + " \"callbackReference\": \"http://my.callback.com/mec_service_mgmt_ser_availabilities/terminate\", \n", + " \"appInstanceId\": appInstanceId\n", + "}\n", + "method = \"POST\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZMAAAAvCAYAAACWulRZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACESSURBVHhe7d19cFRlni/wb6chsvbooHeq9zppKtCWyNadtUazU4CG5o6yOpt1QkQJ8bY1kp250WWBnQACorAlccC4EBS4jJMazAxFjyFASBg2I17QIgaTlJvBq5SXQQ2k6BR3urwSvbYLCd25fzzn5TlPn3O6O4QQ4Pup6iL0efr0c5637v6d5zzH09/fPwgiIiIiIiIiIiIiIhc56hNERERERERERERERCqP08zkwUHxtP4vEREREREREREREV0fPB6P5V/YBZMHBweNRzKZNP4mIiIiIiIiIiIiomufx+OBx+NBTk6O8bfH47EGk/XAcSKRwMWLF5FIJJBIJJBMJo3tRERERERERERERHTt0Wch5+TkwOv1wuv1YsyYMfB6vanB5GQyiUQigf7+fvR86cHhqA8nv/CiPyHvkoiIiIiIiIiIiIiuVbleYPKtCTwQiCP/24PIzc0VAWU9mKwva9Hf349T5wbxyw9vRtEdgyi4LYkbvOruiIiIiIiIiIiIiOhadCEBdJ3NQcsnHvzjXV9h0i0e5ObmIkdOlEwmcfHiRRyO+lB0xyDuDTCQTERERERERERERHQ9ucEL3BtIouiOQRyO+nDx4kUkk0kzmCzfdO/kF14U3CbWSSYiIiIiIiIiIiKi60/BbSJWnEwmMTg4aJ2ZbCx1kRDRZyIiIiIiIiIiIiK6Pt3gBfoTYkWLlGAytIAyERERERERERERERGkmLFxA75kMomBgQHE43E82+7Hy7MS6msAAE0nPOg6m4ML9ptJcYNXTAcvmcIgPREREREREREREV1dlh/yYv30GHw+X+rMZDdNJzx4L8pAcjYuJID3ojloOuFRNxERERERERERERFdNbIKJnedzSo5SVh2REREREREREREdDXLKsLJGclDx7IjIiIiIiIiIiKiq1lWwWQiIiIiIiIiIiIiuj4xmExEREREREREREREaTGYTERERERERERERERpMZhMRERERERERERERGkxmEw0WiRiaH91OcKPlaKkeDO61O101YvtqkRJcSnmPrEcdR/E1c1EqaKNWFJcipKURyWao2rioehF86JSLNnV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ERERERERERERjRYMJhMRERERERERERFRWlkFk2/gKhdDxrIjIiIiIiIiIiKiq1lWweSC25LqU5Qhlh0RERERERERERFdzbIKJpdMGcS9gSRn2WbhBi9wbyCJkimD6iYiIiIiIiIiIiKiq4anv79/EACSySQGBgYQj8fxbLsfL89KqGmJiIiIiIiIiIiI6Dqy/JAX66fH4PP5Umcmezwe9SkiIiIiIiIiIiIiuk7pMWNLMNnj8SAnJwe5XuACJyYTERERERERERERXbcuJIBcL5CTkyNix/oGj8djBJMn35pA19mUSctEREREREREREREdJ3oOitixSnBZEBEmMeMGYMHAnG0fOLBe9EczlAmIiIiIiIiIiIiuo5cSADvRXPQ8okHDwTiGDNmjAgo6zfgg3YTvkQigf7+fvR86cHhqA8nv/CinwFlIiIiIiIiIiIioutCrheYfGsCDwTiyP/2IHJzc+H1eq3B5MHBQQwODiKRSODixYtIJBJIJBJIJpPGdiIiIiIiIiIiIiK69hg32svJgdfrhdfrxZgxY0Qg2eOxBpMhBZQHBweRTCaNv4mIiIiIiIiIiIjo2iffX0//2+Px4P8D+retECtDVJ8AAAAASUVORK5CYII=" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check Subscriptions\n", + "\n", + "The GET method may be used to request information about all subscriptions for this requestor. Upon success, the response contains message content with all the subscriptions for the requestor.\n", + "\n", + "![image-3.png](attachment:image-3.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecAppSupportApi.yaml#/appSubscriptions/ApplicationsSubscriptions_GET)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {}\n", + "method = \"GET\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + "print(\"Status Code\", response.status_code) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscriptions_list = response.json()\n", + "pd.DataFrame.from_dict(subscriptions_list['_links']['subscriptions']).reindex(columns=[\"subscriptionType\"]).style" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "## (3) Interacting with the Location API (MEC013)\n", + "\n", + "https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml\n", + "\n", + "For this part of the tutorial, we will use the MEC Location (MEC013) API ```location```, version 3:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mec013_path = mec_base_path + \"/location/v3\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check if the Location Services are available in the choosen MEP \n", + "For this part of the tutorial, we will use theWe will need to use the MEC Service Management (MEC011) API ```mec_service_mgmt```, version 1:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mec011_service_mgmt_path = mec_base_path + \"/mec_service_mgmt/v1\"" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following method retrieves information about a list of mecService resources. This method is typically used in \"service availability query\" procedure\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecServiceMgmtApi.yaml#/services/Services_GET)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_service_mgmt_path + \"/services\"\n", + "payload_dic = {}\n", + "method = \"GET\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "| ![image.png](attachment:image.png) | Look for the mec013 service in the previous replay. If it shows that the service mec013 is **listed** and is **active**, you can proceed to the next step |\n", + "| --------------|-------------- |\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "services_list = response.json()\n", + "pd.DataFrame.from_dict(services_list).reindex(columns=[\"serName\", \"serCategory\", \"version\",\"state\"]).style" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZoAAAAuCAYAAACh86w2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABfASURBVHhe7d1/bBzlncfx9+7GJpflV6OeK/AiEyMo7XHoqK9yAmZzd6RQUkhcII5PiwqmOnPkICI/SCAQTm1oIGl+0JBLW5/AVdRtHacJjqFpqRJQFoNjVT56gFAuFAeLtXKsEDhclyZr7/r+mJndmdnxrr0JqRN/XtIqYWb2mWeenVmUzz7zHV8qlRpBRERERERERERERKREvrEEzSMjxibWnyIiIiIiIiIiIiIyOfh8PsefXgoGzSMjI9lXJpPJ/l1EREREREREREREzn4+nw+fz4ff78/+3StwHjVotkLldDrN8PAw6XSadDpNJpPJrhcRERERERERERGRs48VJvv9fgKBAIFAgClTphAIBDzD5lGD5kwmQzqdJpVK0X/Mx/54kMMfB0il3VuKiIiIiIiIiIiIyNmoPABXTE9zQyhJ1QUjlJeXEwgE8Pv9ju08g2arVEYqleLIJyP8+M3zmXv5CDUXZTgn4N5aRERERERERERERM5GJ9LQe9TP3nd93Hf1p8z4go/y8vJsKQ2LM3a2yWQyDA8Psz8eZO7lI1wbUsgsIiIiIiIiIiIiMpmcE4BrQxnmXj7C/niQ4eHhbHllO8+g2f4AwMMfB6i5KP+NIiIiIiIiIiIiIjI51FxkZMWZTCabH9t5Bs3Yy2ekjdRaRERERERERERERCancwKQShuVMNwhM4WCZsywWURERERERERERESEApmx58MAM5kMQ0NDJJNJHumuYP2ctHsTADoO+eg96ueE92pxOSdgTDGvvzJvyEVEREREREREREQmtBX7Ajw5K0EwGKSsrAy/PzePueCM5kI6Dvl4Pa6QeTxOpOH1uJ+OQ7mnMYqIiIiIiIiIiIic6UoOmnuPlvzWSU9jJyIiIiIiIiIiImeTkhNPzWQuncZOREREREREREREziYlB80iIiIiIiIiIiIiIihoFhEREREREREREZGTpaBZRERERERERERERE6KgmYREREREREREREROSkKmkXONPHdLJ3XQH3eawl74u6NSzHAngcaWLpjwL3iNBtPP3rYZI7D2Lb/vI2n76fC6d6fTFS9Gxqo39Bj/pft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He695SqCp+lyEpGJo3dDA2vIfb+KiIiIiIiITHQr9gV4claCYDBIWVkZfn8ub534QTMAaYaOfWiUNsj6K86Z/gVyvThO6uOPGbZvYpcNpHNGjif482fD+Kd9ialTT+fxjCNofutZ7n70JYZufJzo/WOcYesVhJ51Btn3cDNb37mC5ueeYO4X3etFRCY2Bc0iIiIiIiJypikUNE/s0hlZAcoucNddtofMAFMpz6vNbHu5QmYA39QKpk2/+LSHzGM3RHfHSwxyBZHGsYbMk0Tfb2l/B4I3NipkFhERERERERER+Qs7Q4LmyaqMWavb6ejUjN081Y20dLaPY5a3iMjEUrO8XbOZRURERERE5KyhoPmsVMn8Z9rp6Dxby2aIiIiIiIiIiIjIRKKgWUREREREREREREROioJmERERERERERERETkpJQfN50zU5+edATR2IiIiIiIiIiIicjYpOWiuuSjjXiRjpLETERERERERERGRs0nJQXP9lSNcG8podu44nBOAa0MZ6q8cca8SEREREREREREROWP5UqlUXuqZyWQYGhoimUzySHcF6+ek3ZuIiIiIiIiIiIiIyCSyYl+AJ2clCAaDlJWV4ffn5jEXnNHs8/nci0RERERERERERERkkhotMx41aPb5fPj9fsoDcEITmkVEREREREREREQmrRNpKA+A3+/3DJs9g2afz5cNmq+Ynqb3qOdmIiIiIiIiIiIiIjIJ9B41smIraHaHzaMmyH6/nylTpnBDKMned328HvdrZrOIiIiIiIiIiIjIJHIiDa/H/ex918cNoSRTpkxx1Ga2eD4MEPOBgOl0mlQqRf8xH/vjQQ5/HCClsFlERERERERERERkUigPwBXT09wQSlJ1wQjl5eUEAoG8sHnUoHlkZISRkRHS6TTDw8Ok02nS6TSZTCa7XkRERERERERERETOPlZpDL/fTyAQIBAIMGXKFAKBgGfpjFGDZmxh88jICJlMJvt3ERERERERERERETn72Z/nZ/3dHTID/D9ZiqGq4Ms+3gAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### List of zones \n", + "\n", + "To pick a zone of interest (e.g. zone with the most terminals) lets first check the list the avaliable ones usin the Location APIs. This query retrieves information about one or more specific zones or a list of zones. The output is a list of identifiers for zones authorized for use by the application. \n", + "\n", + "![image-3.png](attachment:image-3.png)[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml#/location/zonesGET)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec013_path + \"/queries/zones\"\n", + "payload_dic = {}\n", + "method = \"GET\"\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zone_list = response.json()['zoneList']['zone']\n", + "pd.DataFrame.from_dict(zone_list).reindex(columns=[\"zoneId\", \"numberOfUsers\", \"numberOfAccessPoints\", \"numberOfUnserviceableAccessPoints\"]).style" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZMAAAAwCAYAAABkOuQXAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABmbSURBVHhe7d19cBPnnQfwrywwHEqahOmpQyXGwQyE9trMUU/HJnXEXUNDS1Pj8GLcUdrEvZ7bMMDFhgChgZuW1mBqm9bhSI454rsMao0Jju2kXLkBMhZKbE9GlxzJZDho7GiQhosmk5hMlRrJku6PfdGzq9WbbRzZ/n5mNLb3Tc+z+zwr70/P/tYUDofjICIiIiIiIiIiIiJKw5QqmByPS5OVn0REREREREREREQ0M5hMJs1PGAWT4/G4+orFYurvRERERERERERERDT9mUwmmEwmFBQUqL+bTCZtMFkJHEejUYyOjiIajSIajSIWi6nziYiIiIiIiIiIiGj6UUYhFxQUwGw2w2w2Y9asWTCbzcnB5Fgshmg0inA4DN8NE877LbjykRnhqLhJIiIiIiIiIiIiIpquCs3A0vlRPGAPoeiOOAoLC6WAshJMVtJahMNhDH0cx7OXPoc1S+IoWRDDHLN+c0REREREREREREQ0Hd2MAt7rBThz1YTH7/0Ei+4yobCwEAXiQrFYDKOjozjvt2DNkjjuszOQTERERERERERERDSTzDED99ljWLMkjvN+C0ZHRxGLxRLBZPGhe1c+MqNkgZQnmYiIiIiIiIiIiIhmnpIFUqw4FoshHo9rRyarqS6iUvSZiIiIiIiIiIiIiGamOWYgHJUyWiQFkyEHlImIiIiIiIiIiIiIIMSM1QfwxWIxRCIRhEIhPNVnxaFVUf06AICuyyZ4rxfgpvFs0pljloaDVy5jkJ6IiIiIiIiIiIimlp3nzDiwIgiLxZI8MjmdrssmvO5nIDkXN6PA6/4CdF026WcRERERERERERERTRk5BZO913NanATcd0RERERERERERDSV5RTh5IjkseO+IyIiIiIiIiIioqksp2AyEREREREREREREc1MDCYTERERERERERERUUYMJhMRERERERERERFRRgwmExEREREREREREVFGDCYTTQX+TtRXVKEy6VWHbr9+4bEIoHtrFepPBvQzJtkkl6O/FZUVrfDqp4+Dt6kKlU0D+smGgifr5OM4sWUYq1zKnj3pmFZW3Ipt30K6tpE4VgfxL2No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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get information about the location of UEs\n", + "The GET method is used to query location information about UEs (User Equipments) .\n", + "\n", + "![image-2.png](attachment:image-2.png)[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml#/location/userSubListGET)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec013_path + \"/queries/users\"\n", + "payload_dic = {}\n", + "method = \"GET\" \n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "user_list = response.json()['userList']['user']\n", + "pd.DataFrame(user_list, columns=[ \"address\", \"zoneId\", \"accessPointId\", \"locationInfo\", \"timestamp\"]).style " + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB44AAAA+CAYAAADUDDb+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAC3CSURBVHhe7d1/VNT3ne/xJ44YhUSgBbwlKAmLUevFJuJmx96ge0NyxXYhRtlEemyIpkRrbCTXwtbgaTAn6C6sJ2iNgbCRkHqCpmpSuEbYlpwKnjprgyayibqxJCChq9iCJoOWcfT+8Z1fzAwwoCYaX49z5gS/8/kOn/l+vt8hZ17f9+cT1Nvbe5krcPnyFe0uIiIiIiIiIiIiIiIiIiLXQFBQkPemfgUNJzj2DIsVHIuIiIiIiIiIiIiIiIiIXH88g+PBQuQhBcfOkPjy5cuuh+d2ERERERERERERERERERH56jmD4qCgINfDc7u3gINjz7D40qVLXLp0qc82hcciIiIiIiIiIiIiIiIiIl89z7A4KCiIESNGMGLECJ8Quc8+gQTHnoGx3W53PbwDZBERERERERERERERERER+Wp5B8Ymk8n18AyQ++wzWHDsHRpfvHiRE+c7qfv8GM09HZyzX/DeRUREREREREREREREREREvmJjTaNJDIlhzm2TSRgTxciRI/sNjwMKji9dusTFixe5ePEiv/v8Y147c5AFUdP527A7iRg5xnsXERERERERERERERERERH5inVdPM8fzn7Crs5DPB55L39/20RGjhzJyJEjXeGx04DBsbPa2G63Y7PZ+LjnNC/897/z7B1ziRv9Te/mIiIiIiIiIiIiIiIiIiJynWm98GfWfbqXNf/j/zAxJJrg4GBMJlOfquMR3jt585ymuu7zYyyImq7QWERERERERERERERERETkBhE3+pssiJpO3efHsNvtXLp0icuX+9YXBxQcO8Pj5p4O/jbsTu8mIiIiIiIiIiIiIiIiIiJyHfvbsDtp7ulwhcYBB8eejZ3B8Tn7Ba1pLCIiIiIiIiIiIiIiIiJyg4kYOYZz9gt9qo09M+F+g2MnZ2PvxFlERERERERERERERERERG4s/eW/gwbHeCXNIiIiIiIiIiIiIiIiIiJyY+ov+w3q7e313eqxg91up7e3lwsXLrDksx1UfPtx76YuJy90UX3mfZq/+Iy/Xrro/bR8hW4ZMZLEW28nPfJuxo+O8H5aRERERERERERERERERL7mFn/0Gltvf5TRo0czatQoTCYTQUFBBAUFBVZxHIiTF7pY/+k7vHeuVaHxdeivly7y3rlW1n/6DicvdHk/LSIiIiIiIiIiIiIiIiI3sasWHFefeZ/zl2zem+U6c/6Sjeoz73tvFhEREREREREREREREZGb2FULjpu/+Mx7k1ynNFYiIiIiIiIiIiIiIiIi4umqBceanvrGobESEREREREREREREREREU9XLTgWEREREREREREREREREZEbk4JjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbXFBvb+9l740Aly9f5vLly9jtdnp7e7lw4QJLPttBxbcf924KwOKPXvPedO2Nm8+vYscT6r3dw8k/b2Tpp95bDfPvWsaPbrvFa+tf+aC9lNWnvDZ7WDl1JXNG991m/byRf/yvQ303BtA/J7/7X0P9jeMVsTZQnFmBxQ6TsktYNy/Mu8U10ETJ9zfTCBA3n5e3pBHt3eRGYW2i/Cdl1J6C2LRnWLdsSkDnzrXSvquQZytPYB07heWFeaTEebe4QdjbqMp+jp2nIHROHq8/PcW7hQym6yi1lbvZ+x+ttJ+zAcGEjo8jJfNJfjA7imDv9teYrcPCa5VneWj1nBv3eh+i01X5/HgbZJUWkj7e+9kvyYEyFrxgIXlNBTkzvZ8cQNdRqjd8QOwLC5kOQAfVy/Op5Ev8zLaf5fibZRz6mzwy7/V+UgLmcxz9jKW9g/q1JVS+34nVDrGZP+DOqjdonL2CXXlJ3q94ZXz6c51cKyIiIiIiIiIiItexxR+9xtbbH2X06NGMGjUKk8lEUFAQQUFBN3jF8aVL+E29PYz/5kp+dZfxVbXbA5QlrfQTGgPcwndiV/LO3fOZ7/3UuPn8Ksk3NAYIvS3Zd58A+ud02R5oy+tXy87/h8UOmKaRPufLCI2/Xk5X76b2lA2w0V7TwHHvBleg+3ANlesLWZn5HNUnvZ/1p4mdW09gtTtCw993eDe4Ydj27WLnKYAo5v6DQuOhsu4v44msIsr39RCbuoDcvKXkZqeSSCvVRXksK2rC6r3TNda8rYzak73em+U6dbp2G5WHz3pv/nJ1NPBv245yyu79hAxJIMfxwG62NHUSMXsh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MUAMc5/PN/riuV7uqBBip84KvBJYRERERERERERERETE4cauOJZh628cB2fDsvZJig8SWFWnXFX9VrEKLZV55L7ZCaZp5O54JsB1t0VkuFRxLCIiIiIiIiIiInLj+fpWHMuX78y7VB80fjQvSlNoLNcH+1Hq93QCEL1ggUJjERERERERERERERGRIVJwLEMTOYd1eyrYtaeC3PsHm6Ra5EtimkL2m8Z5+XLWBO9nReQaiM4sZNceVRuLiIiIiIiIiIiIfF0oOBYRERERERERERERERERuckpOBa5gUzPM6pqVeUnIiIiIiIiIiIiIiIiV5OCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm9xVC45vGTHSe5NcpzRWIiIiIiIiIiIiIiIiIuLpqgXHibfe7r1JrlMaKxERERERERERERERERHxdNWC4/TIuxkzIth7s1xnxowIJj3ybu/NIiIiIiIiIiIiIiIiInITu2rB8fjREay+43vMGBunqZCvQ7eMGMmMsXGsvuN7jB8d4f20iIiIiIiIiIiIiIiIiNzEgnp7ey97bwS4fPkyly9fxm6309vby4ULF1jy2Q4qvv24d1MREREREREREREREREREbnOLf7oNbbe/iijR49m1KhRmEwmgoKCCAoKCqzi2NlYRERERERERERERERERERuXP1lv4MGx84d/e0sIiIiIiIiIiIiIiIiIiI3jv7y336DY8/GQUFBjBgxgrGm0XRdPO/dVERERERERERERERERERErmNdF88z1jSaESNG9MmBnT/3Gxw7ORuPGDGCxJAY/nD2E+8mIiIiIiIiIiIiIiIiIiJyHfvD2U9IDIlxBccBVxw7OUNjk8nEnNsms6vzEK0X/uzdTERERERERERERERERERErkOtF/7Mrs5DzLltMiaTqU/VsVNQb2/v5T5bvFy+fJlLly5x8eJFLl68yO8+/5jXzhxkQdR0/jbsTiJGjvHeRUREREREREREREREREREvmJdF8/zh7OfsKvzEI9H3svf3zaRkSNHMnLkSJ/wOKDg2Bke2+12Ll68yInzndR9fozmng7O2S947yIiIiIiIiIiIiIiIiIiIl+xsabRJIbEMOe2ySSMiWLkyJF9Ko6HFBzjJzx2Pi5dusSlS5dcz4uIiIiIiIiIiIiIiIiIyFfLGQqPGDHCtSyx8+EvNCbQ4BiP8NgZIHsGxgqORURERERERERERERERESuD85g2DtA9tzmLeDgGEd47PyvZ1is0FhERERERERERERERERE5PrhDIe9w2J/oTHA/wf0qsMboSHotwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subscribe zoneLocationEventSubscription (notifies every time there is a zone change)\n", + "\n", + "Subscribe to the service that notifies everytime there is change in a zone.\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml#/location/zoneSubPOST)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec013_path + \"/subscriptions/zones\"\n", + "payload_dic = {}\n", + "method = \"POST\"\n", + "\n", + "payload_dic ={\n", + " \"zoneLocationEventSubscription\": {\n", + " \"subscriptionType\": \"ZoneLocationEventSubscription\",\n", + " # URI exposed by the client on which to receive notifications via HTTP\n", + " \"callbackReference\": \"http://my.callback.com/zone-location-event-notification/zone3\", \n", + " # Identifier of zone (e.g. zone001) to monitor.\n", + " \"zoneId\": \"zone03\",\n", + " # A correlator that the client can use to tag this particular resource representation during a request to create a resource on the server. \n", + " \"clientCorrelator\": \"0123\",\n", + " \"requestTestNotification\": True,\n", + " # List of the users to be monitored. If not present, all the users need to be monitored.\n", + " \"addressList\": [\n", + " \"10.1.0.1\",\n", + " \"10.100.0.1\",\n", + " \"10.10.0.1\",\n", + " \"10.100.0.2\",\n", + " \"10.100.0.3\"\n", + " ],\n", + " \"reportingCtrl\": {\n", + " # Maximum number of notifications. Default is 0 (no max value)\n", + " \"maximumCount\": 150,\n", + " # Maximum frequency (in seconds) of notifications per subscription.\n", + " \"maximumFrequency\": 0,\n", + " # Minimum interval between reports in case frequently reporting. Unit is second.\n", + " \"minimumInterval\": 5\n", + " },\n", + " # List of user event values to generate notifications for.\n", + " \"locationEventCriteria\": [\n", + " \"ENTERING_AREA_EVENT\", \"LEAVING_AREA_EVENT\"\n", + " ],\n", + " \"expiryDeadline\": {\n", + " \"seconds\": 1977836800,\n", + " \"nanoseconds\": 0\n", + " }\n", + " }\n", + "}\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# check subscription \n", + "# GET /subscriptions/zones/{subscriptionId}\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB48AAABACAYAAAAK5jFxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACxASURBVHhe7d1/VNT3ne/xJ44QZRLBFvSG4I9ysWq92Ebc7NgbdG9JF2wKTdVG6bEhmhKtsUquha0hp2KO6F5YT9AmBmQjIeUETcSksEbYhJwKnjq1QVNpoq6WKBKyCi1gMmoZR+4f83sYYPBHosnrcc6cwHc+3+Ez3+/nO/F8X/P+fIJ6enp6ERERERERERERERERERGRL7Wgaw2Pe3uvaTcREREREREREREREREREbmJgoKCfDcFZEjhsWdgrPBYREREREREREREREREROTW4xkeDyVIDig8dgbFvb29rofndhERERERERERERERERER+fw5w+KgoCDXw3P7QAYNjz0D46tXr3L16lWvbQqQRUREREREREREREREREQ+f56BcVBQEMOGDWPYsGF9guT+DBgee4bGNpvN9fANkUVERERERERERERERERE5PPlGxobDAbXwzNE7k+/4bFvcHzlyhVOXWqn9pPjNF1s44Ltsu8uIiIiIiIiIiIiIiIiIiLyORtlGEFcaBRJd00hdmQkw4cPDyhAHjA8vnr1KleuXOHKlSv87pOTvNRxiPmRM/iHsK8xevhI311ERERERERERERERERERORz1nnlEn/s/pDK9sM8GnEf/3TXJIYPH87w4cNdAbI/fsNjZ9WxzWbDarVy8uJ5Nvz3f/LUxLlMGPFV3+YiIiIiIiIiIiIiIiIiInKLOXP5r2w8vY+n/8c/Myl0DMHBwRgMhn6rj4f5bnDynLK69pPjzI+coeBYREREREREREREREREROQ2MWHEV5kfOYPaT45js9m4evUqvb19aotdBgyPnQFy08U2/iHsa75NRERERERERERERERERETkFvYPYV+j6WKbKzgeUnjsuYMzPL5gu6w1jkVEREREREREREREREREbjOjh4/kgu2yV9VxfyFyn/DYybmDv51EREREREREREREREREROT2EUj+2294zACJs4iIiIiIiIiIiIiIiIiI3D4CyX6Denp6vFo4d7LZbPT09HD58mWWfrSL0m886tnMy9nLnVR1vEfTpx/x96tXfJ+Wz9Edw4YTd+c9pEZ8i3EjRvs+LSIiIiIiIiIiIiIiIiJfcEs+eIkd9yxkxIgRhISEYDAYCAoKIigoyKvdgJXHgTh7uZNNp9/k3QtnFBzfgv5+9QrvXjjDptNvcvZyp+/TIiIiIiIiIiIiIiIiIiJwI8Ljqo73uHTV6rtZbjGXrlqp6njPd7OIiIiIiIiIiIiIiIiICNyI8Ljp0498N8ktSudKRERERERERERERERERPpz3eGxpqq+fehciYiIiIiIiIiIiIiIiEh/rjs8FhERERERERERERERERGR25/CYxERERERERERERERERERUXgsIiIiIiIiIiIiIiIiIiIKj0VEREREREREREREREREROGxiIiIiIiIiIiIiIiIiIig8FhERERERERERERERERERACCenp6ej039Pb20tvbi81mo6enh8uXL7P0o12UfuNRz2YuSz54yXfTzTd2Hq9Fj8Pou93D2b9uYdlp3612876+nJ/edYfP1r/zp9Yi1p7z2exh9bTVJI3w3mb5pIEf/ddh740B9M/J7/43UX/n8bpY6ilIK8Vsg8kZhWx8KMy3xU3QSOGDz9EAMGEeL2xLYYxvk9uFpZGSnxdTcw6iU55k4/KpAY2dm6W1Mo+nyk5hGTWVFXnZJE7wbXGbsLVQkbGO3efAmJTNy6um+rb48ulpx1xRTtVbxzjRaQXAOHo8MxYsIiNlKkaD7w43Wecxqjb/iegNi5jh+9wtzfH5M2clldnxvk9+RtqoWpFDGUP9/LNyfn85r3QmkflQlH3TwWLmbzCT8HQpmbN8298c1jYzL5V184O1SUPou+D32PUdj5Yj5eTl13PighUM8WRlBVPwrzfnHAfSHxEREREREREREfn8LfngJXbcs5ARI0YQEhKCwWAgKCiIoKAgr3a3Z+Xx1at4Jd5+jPvqal77um8c8QDF8av9BMcAd/DN6NW8+a15zPN9auw8XovvGxwDGO9K6LtPAP1z6rUF2vLW1bz7PzDbAMN0UpM+i+D4i+V81R5qzlkBK63V9ZzwbXAduo5UU7Ypj9Vp66g66/usP43s3nEKi80e7NX8vs23wW3Dur+S3ecAIpn7fQXH1uZqnlqYTUHlGUJmppCZvYysVfMwje2moSSfRzKrabX57nVzna8pp+xIt+9muamO8kp+PR9d8t3+2WoqL6bmbI/vZgnA4MeujX3FdZwglgWrlpG1Jpkpw33b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bsRo36dFRERERERERERERERERAAI6unp6fXc0NvbS29vLzabDavVyuXLl1nSupPSbzzq2UxERERERERERERERERERG4DSz54idLoRYwYMYLg4GAMBgNBQUEEBQV5teu38tjZ2HcHERERERERERERERERERG5vQSS//YJjz13CAoKYtiwYYwyjKDzyiXfpiIiIiIiIiIiIiIiIiIicgvrvHKJUYYRDBs2zCsH9hci9wmPnZw7DBs2jLjQKP7Y/aFvExERERERERERERERERERuYX9sftD4kKjXOGxv9DYacDweNiwYRgMBpLumkJl+2HOXP6rbzMREREREREREREREREREbkFnbn8VyrbD5N01xQMBoNX9bE/QT09Pb2+GwF6e3u5evUqV65c4cqVK/zuk5O81HGI+ZEz+IewrzF6+EjfXURERERERERERERERERE5HPWeeUSf+z+kMr2wzwacR//dNckhg8fzvDhwwcMkAcMj50Bss1m48qVK5y61E7tJ8dputjGBdtl311ERERERERERERERERERORzNsowgrjQKJLumkLsyEiGDx/uVXk85PAYPwGy83H16lWuXr3qel5ERERERERERERERERERD5fzmB42LBhriWKnY/BgmMGC4/xCJCdIbJnaKzwWERERERERERERERERETk1uAMh31DZM9tAxk0PMYRIDv/6xkYKzgWEREREREREREREREREbl1OANi38B4sOCYQMNjJ8+wWMGxiIiIiIiIiIiIiIiIiMitxzMoDiQ0dhpSeOxJ4bGIiIiIiIiIiIiIiIiIyK1nKIGxp/8P7IpKeB2+xjMAAAAASUVORK5CYII=" + }, + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB44AAABDCAYAAABjsCjhAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAADEKSURBVHhe7d1/VNTXve//J4xjIpMKpoI3BNFytGpzsQnkpGNv0HVD8hWbA8coJ5EuK1FLtGoiWTlwr8FvxNyg58LXFbRqIDQiKSuYFI2FGvEmZFVwHadp0FSaoNWSiIRcgRawGUwYR79/zAwMwwADYuKP12OtycLP7M+wZ+/9+ZC135/33n5dXV1XEBERERERERERERERERGRW5a/5wEREREREREREREREREREbm1KHAsIiIiIiIiIiIiIiIiInKLU+BYREREREREREREREREROQWp8CxiIiIiIiIiIiIiIiIiMgtzq+rq+uK58GhuHLlqk4XEREREREREREREREREZFvgJ+fn+ehbsMOHF+5cqX7dfny5e5jIiIiIiIiIiIiIiIiIiJyfXAFi/39/fHz8+t+eRpW4NgVLLbb7Vy6dAm73c7ly5e7A8kiIiIiIiIiIiIiIiIiIvLtcgWJ/f39MRgMjBo1CoPB0B1E7lV2qIHjK1euYLfbsdlsnO5spuJCHbWdTVywf+VZVEREREREREREREREREREvmVjDbcTGRBK3NgZTA0IwWg0YjAYegWPhxQ4dmUa22w23u84RWHLH1gYHMU/B36PcaPGeBYXEREREREREREREREREZFvWduli/yx41P2thxjafCPeChwGkajsVfm8ZACx5cvX+bSpUuc/McX/K8vDvH85HlMuv27nsVEREREREREREREREREROQ6c/arv7Hps4P8v3fNZfp37mLUqFH4+/sD4PivD1z7F1+6dImKC3UsDI5S0FhERERERERERERERERE5AYx6fbvsjA4iooLdVy6dKk7BsxQAsc4M47tdju1nU38c+D3PN8WEREREREREREREREREZHr2D8Hfo/azibsdjuXL1/uPj6kwDHO4PEF+1fa01hERERERERERERERERE5AYzbtQYLti/6hU0ZqiBY/dUZRERERERERERERERERERuTF5xn6HFDjG+QEiIiIiIiIiIiIiIiIiInLj8oz7+nV1dfkUCb5y5Qo2mw2r1cqT50oo/MGTnkW6nfuqjbLWj6j98nO+vnzJ8235Ft3mP4rI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ilm+rhLrxGh++tj9hNzewamyg5Sd7NCKDSIiIiIiIiIiMqJu3oxj+ea1vk/ZB44fzYvjFTSW64O9jsoDjmWSQxYuVNBYRERuLDMXs23zAqJx7vWbvYfKC8EkuO+9LiIiIiIiIiIico0o4/gW118/yvVNGcciIiIiIiIiIiIiIiIyXMo4FhERERERERERERERERGRPhQ4FrkBRaUXsvdAIXsPKNtYRERERERERERERERErp4CxyIiIiIiIiIiIiIiIiIitzgFjkVEREREREREREREREREbnEjHji+zX+U5yG5TqmvRERERERERERERERERIRrETiOvONuz0NynVJfiYiIiIiIiIiIiIiIiAjXInCcMP5exvgbPQ/LdWaMv5GE8fd6HhYRERERERERERERERGRW9CIB44n3j6OdZN/wv1jJ2kp5OvQbf6juH/sJNZN/gkTbx/n+baIiIiIiIiIiIiIiIiI3IL8urq6rnge9ObKlSvYbDasVitPniuh8AdPehYREREREREREREREREREZHr3NJPdrN7YhImkwmj0Yifn9/QM479/Pw8D4mIiIiIiIiIiIiIiIiIyA3EM+47pMCxn59fnw8QEREREREREREREREREZEbi2fsd0iBYwB/f3/GGm6n7dJFz7dEREREREREREREREREROQ61nbpImMNt+Pv3ztUPKTAsb+/PwaDgciAUP7Y8ann2yIiIiIiIiIiIiIiIiIich37Y8enRAaEYjAYegWPfQ4cu1KVR40aRdzYGextOcbZr/7mWUxERERERERERERERERERK5DZ7/6G3tbjhE3dgajRo3qtVy1X1dX1xXPE/pz5coVLl++jM1m4/2OUxS2/IGFwVH8c+D3GDdqjGdxERERERERERERERERERH5lrVdusgfOz5lb8sxlgb/iIcCp2E0GvH39x9e4Bhn8Nhut2Oz2Tjd2UzFhTpqO5u4YP/Ks6iIiIiIiIiIiIiIiIiIiHzLxhpuJzIglLixM5gaEILRaMRgMHQHjRlO4Bi3zGO73c6lS5ew2+1cvnyZK1eucOXKkD9ORERERERERERERERERERGmGspan9/fwwGA6NGjere29g9aMxwA8c4g8eu1+XLl7uPiYiIiIiIiIiIiIiIiIjI9cEVIHYFi933Ne5VbriBYxcFi0VERERERERERERERERErn/eAsYuVx04FhERERERERERERERERGRG5u/5wEREREREREREREREREREbm1/P9coqY03yVljgAAAABJRU5ErkJggg==" + }, + "image-4.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB4cAAAA/CAYAAADjRc6RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACq8SURBVHhe7d1/VFXnne/xtxxBhcQfLeANwR/lag11tK2kKfYGnVuSAetAE/Um0uVI0KJWUyXLC1MlK2JW0BkYV9AxBmQiIeMqJlWTwpjApGRVcI3npkFTaaJWSvxByCi0/kgOWvDI/eP82mdzgAMYg/p5rbVXce9nn/Oc59n7pGt/z/f7DGlvb+9ERERERERERERERERERETuaEP6Ehzu7PS7qYiIiIiIiIiIiIiIiIiI3CJDhgwx7+qi1+CwMSCs4LCIiIiIiIiIiIiIiIiIyOBjDA53FyjuNjjsCgR3dna6N+N+ERERERERERERERERERH56rmCwUOGDHFvxv3udr6Cw8aA8I0bN7hx44bXPgWIRURERERERERERERERES+esaA8JAhQwgICCAgIKBLoBhfwWFjUNhut7s3c5BYRERERERERERERERERES+WuagsMVicW/GIDHm4LA5MHz9+nUarrZQ9fkJ6tuauWK/ZnwfEREREREREREREREREREZBEZahjMtOIKEex9g0ogwhg4d2iVA3CU4fOPGDa5fv87169f57eeneLX1feaHzeB7o77BmKEjvN9BRERERERERERERERERES+chevX+V3lz9hX8sRngp9iL+9dzJDhw5l6NChngCxKzjsyhq22+10dHRwqu0CL/z3f7J+4hwmDP+6+bVFRERERERERERERERERGSQOXPtz2w6/Q7P/o+/Y3JwOIGBgVgsFkfpaWNDY0npqs9PMD9shgLDIiIiIiIiIiIiIiIiIiK3iQnDv878sBlUfX4Cu93OjRs36Ox0FJPuEhx2BYjr25r53qhvGA+LiIiIiIiIiIiIiIiIiMgg971R36C+rdkdGPYKDht3uILDV+zXtMawiIiIiIiIiIiIiIiIiMhtZszQEVyxX/PKGu7s7PSdOexqICIiIiIiIiIiIiIiIiIitydz/NcrOGxsICIiIiIiIiIiIiIiIiIity9z7HdIe3t7p2un3W6nvb2da9euseTT1yn51lNeJxudu3aR8tYPqf/iU/5647r5sHyFhgUMZdo995Mc+h3GDR9jPiwiIiIiIiIiIiIi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JvXh8YQbM8NHjGJKTBKZxQVdswwtESQXFDg/Q7Dn844YxZSHF7IpvecA8JfFU/I8kNGTY0h9No9CX4FhHFmZ2YXLvdbsvRkik7LYVpDm/bpBwUROjSezuMATGO6XPs5VazX7fuMIxE5Jju85MAwwYjorC54mcYIrWzOQyKlJZO/ayAIfGaF9vY6xRBC/uYCXs0zXTVAwkVNnkfpsLqseMrTvkwjmPJ/NyqRJRI4xjI3ztf3O6BURERERERERERERkbve7ZM5LP3W3Tz2rgPrxmXkv49/2ZlyU/nOsBWAxtIsMt9oAct0Ml9/xs91sEXuZMocFhERERERERERERFvt3fmsNx6re9R7lwnNXZRkgLDMjjYj1N9oAWA8PnzFRgWERERERERERERERHxk4LD0r3QBDYdKGHfgRIyf9hbQWmRW8QSTfobjuvy5dTx5qMid6kIkneUsE9ZwyIiIiIiIiIiIiLSAwWHRURERERERERERERERETuAgoOiwxSM7Ic2bH7Dmi9YRERERERERERERERERk4BYdFRERERERERERERERERO4CCg6LiIiIiIiIiIiIiIiIiNwF+hUcHhYw1LxLBinNlYiIiIiIiIiIiIiIiIjQ3+DwtHvuN++SQUpzJSIiIiIiIiIiIiIiIiL0NzicHPodRgQEmnfLIDMiIJDk0O+Yd4uIiIiIiIiIiIiIiIjIXahfweFxw8ewbuKPeHDkBJUtHoSGBQzlwZETWDfxR4wbPsZ8WERERERERERERERERETuQkPa29s7Ozs76ezsxG63097ezrVr11jy6euUfOspc3sRERERERERERERERERERnk0j5+lV33P8nw4cMJCgrCYrF0zRweMmQIQ4YMMe8WEREREREREREREREREZHbiDn26xUcdh1UcFhERERERERERERERERE5PZmjv8GGHe6/g4ICGCkZTgXr1/1PltERERERERERERERERERAa1i9evMtIynICAAO84sLGRK0gcEBDAtOAIfnf5E+NhEREREREREREREREREREZ5H53+ROmBUe4g8NemcMursCwxWIh4d4H2NdyhDPX/mxsIiIiIiIiIiIiIiIiIiIig9SZa39mX8sREu59AIvF4p093N7e3ulq2NnZyY0bN7h+/TrXr1/nt5+f4tXW95kfNoPvjfoGY4aOML6uiIiIiIiIiIiIiIiIiIgMAhevX+V3lz9hX8sRngp9iL+9dzJDhw5l6NChngxic3DYFSC22+1cv36dhqstVH1+gvq2Zq7Yr3m/g4iIiIiIiIiIiIiIiIiIfOVGWoYzLTiChHsfYNKIMIYOHeqVOdwlOIyPALFru3HjBjdu3HAfFxERERERERERERERERGRr5Yr8BsQEOBeQti1mdcc7hIcxhAgdgWJjUFhBYdFRERERERERERERERERAYHd1awKUhs3Odu6ys4jDNA7PpfY0BYgWERERERERERERERERERkcHDnRlsCggbA8MA/x9LdujmLSy7GwAAAABJRU5ErkJggg==" + }, + "image-5.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB44AAAA+CAYAAADUDDb+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAC3CSURBVHhe7d1/VNT3ne/xJ44YhUSgBbwlKAmLUevFJuJmx96ge0NyxXYhRtlEemyIpkRrbCTXwtbgaTAn6C6sJ2iNgbCRkHqCpmpSuEbYlpwKnjprgyayibqxJCChq9iCJoOWcfT+8Z1fzAwwoCYaX49z5gS/8/kOn/l+vt8hZ17f9+cT1Nvbe5krcPnyFe0uIiIiIiIiIiIiIiIiIiLXQFBQkPemfgUNJzj2DIsVHIuIiIiIiIiIiIiIiIiIXH88g+PBQuQhBcfOkPjy5cuuh+d2ERERERERERERERERERH56jmD4qCgINfDc7u3gINjz7D40qVLXLp0qc82hcciIiIiIiIiIiIiIiIiIl89z7A4KCiIESNGMGLECJ8Quc8+gQTHnoGx3W53PbwDZBERERERERERERERERER+Wp5B8Ymk8n18AyQ++wzWHDsHRpfvHiRE+c7qfv8GM09HZyzX/DeRUREREREREREREREREREvmJjTaNJDIlhzm2TSRgTxciRI/sNjwMKji9dusTFixe5ePEiv/v8Y147c5AFUdP527A7iRg5xnsXERERERERERERERERERH5inVdPM8fzn7Crs5DPB55L39/20RGjhzJyJEjXeGx04DBsbPa2G63Y7PZ+LjnNC/897/z7B1ziRv9Te/mIiIiIiIiIiIiIiIiIiJynWm98GfWfbqXNf/j/zAxJJrg4GBMJlOfquMR3jt585ymuu7zYyyImq7QWERERERERERERERERETkBhE3+pssiJpO3efHsNvtXLp0icuX+9YXBxQcO8Pj5p4O/jbsTu8mIiIiIiIiIiIiIiIiIiJyHfvbsDtp7ulwhcYBB8eejZ3B8Tn7Ba1pLCIiIiIiIiIiIiIiIiJyg4kYOYZz9gt9qo09M+F+g2MnZ2PvxFlERERERERERERERERERG4s/eW/gwbHeCXNIiIiIiIiIiIiIiIiIiJyY+ov+w3q7e313eqxg91up7e3lwsXLrDksx1UfPtx76YuJy90UX3mfZq/+Iy/Xrro/bR8hW4ZMZLEW28nPfJuxo+O8H5aRERERERERERERERERL7mFn/0Gltvf5TRo0czatQoTCYTQUFBBAUFBVZxHIiTF7pY/+k7vHeuVaHxdeivly7y3rlW1n/6DicvdHk/LSIiIiIiIiIiIiIiIiI3sasWHFefeZ/zl2zem+U6c/6Sjeoz73tvFhEREREREREREREREZGb2FULjpu/+Mx7k1ynNFYiIiIiIiIiIiIiIiIi4umqBceanvrGobESEREREREREREREREREU9XLTgWEREREREREREREREREZEbk4JjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbXFBvb+9l740Aly9f5vLly9jtdnp7e7lw4QJLPttBxbcf924KwOKPXvPedO2Nm8+vYscT6r3dw8k/b2Tpp95bDfPvWsaPbrvFa+tf+aC9lNWnvDZ7WDl1JXNG991m/byRf/yvQ303BtA/J7/7X0P9jeMVsTZQnFmBxQ6TsktYNy/Mu8U10ETJ9zfTCBA3n5e3pBHt3eRGYW2i/Cdl1J6C2LRnWLdsSkDnzrXSvquQZytPYB07heWFeaTEebe4QdjbqMp+jp2nIHROHq8/PcW7hQym6yi1lbvZ+x+ttJ+zAcGEjo8jJfNJfjA7imDv9teYrcPCa5VneWj1nBv3eh+i01X5/HgbZJUWkj7e+9kvyYEyFrxgIXlNBTkzvZ8cQNdRqjd8QOwLC5kOQAfVy/Op5Ev8zLaf5fibZRz6mzwy7/V+UgLmcxz9jKW9g/q1JVS+34nVDrGZP+DOqjdonL2CXXlJ3q94ZXz6c51cKyIiIiIiIiIiItexxR+9xtbbH2X06NGMGjUKk8lEUFAQQUFBN3jF8aVL+E29PYz/5kp+dZfxVbXbA5QlrfQTGgPcwndiV/LO3fOZ7/3UuPn8Ksk3NAYIvS3Zd58A+ud02R5oy+tXy87/h8UOmKaRPufLCI2/Xk5X76b2lA2w0V7TwHHvBleg+3ANlesLWZn5HNUnvZ/1p4mdW09gtTtCw993eDe4Ydj27WLnKYAo5v6DQuOhsu4v44msIsr39RCbuoDcvKXkZqeSSCvVRXksK2rC6r3TNda8rYzak73em+U6dbp2G5WHz3pv/nJ1NPBv245yyu79hAxJIMfxwG62NHUSMXshuXlL+VHyNfz/gUD6IyIiIiIiIiIiIgG7sYPjAIXeNpWVrn89QFnSVAYtQjGN50dTH+izaWXkINXDpvFk+oTUNwn7Uer3dAIQ+kAq5jHeDWQw0enzSR0XDAQTmzaLSd4NrkDLb3ZTvf8E7ecuej/VjyQyliQQagIippD63RjvBjeIs9TvOWL8ODmFufHez8uAWndTsN5Cd1waG3cUkps1B/NsM+Z588ktLWFdWgzd+zZTXPMVh4Ly5Zi5lF17hlht7FcM6Vsq2PVlVRvLNeQ7lqdP/gmIIeUR4/MiMe5ecvZUXP1q435EZxaya4+qjUVERERERERERIbjxp6qOmoeb06I41bgi7/8hocOHvB4cib/mvIg9zjmUG3780aWfeo9PfUFDh8r4qeeU1knLqf+9kjXP91TXT9AadJUJgBwhr21W/hXf/tcPMm/fbCb3QD2v9L7lwvuquM7fsivJ9/pt79BobcxKuTLy/H7G8fhsr37Igs3HAESyK7MJ9V9CK8xj6mqpy7i1aIUwr2bCIeKFlO4DyDm5pq+s2U3P/5JDacJIfWFl8i+x7uB9M+GZe2TFB8c4Jq2H6U8azP/OWMJ63KSCD1QxoIX2shcPYv/LNlOc28wsQvy2JiVAL2dWLZsZsu+Nqy9EDx2AuZHf0j2vASPG3J6OP72Vip3HuF4lw2A4LExJKUvZnlmAqHOaXFb3V2IX1RIcaZxY4P1SA1bflGDpcMGpmBiJ88ic9UizOPc7bv3V1BUesB4fVMw4fEzyc5bhDlmoAm3B+vXAE41Ub7+lzS2nMVqh9CICSRnr+Bx5xTfjmPmc116bXdOv5v5/Hy6Xi2jttUGY6IwL1jM8kemGDd5AFhPUP0vFexs7nAc5xiS0hf1bQN0799GSWUDzf6OVX/jeFd9n6mqjT5NIHfrDJrX+u+T+7PHYfYKduV9y3d6Y2yc3reDLdv66RMex6RoMex4hZ3vd2K1BxM6cSbLVy/uM859OKbYdjOTv2epMW1211FqS7fzxsGBzsu+jPfd3ywMHp+xvZ1Ytr5C1W9P0H4eGBNF4gOPkpOdRLhjLPocw/Vbqf+4B5spmNi701j+T2lM8uiErcPCa0W/NNoQTOjEafwgewmpU0PcjXwYfyM/W5TPj6hhy64jtJ833mfKUyvIvi/K3XSw/vo9jmm0u8YyCYvX9Wm0mUHD9zf7TFU94DmIce1UbthN/cfGuey6Xp1j7bc/S4n1M1W19Ugd5VursfR77AI/TsP7DBEREREREREREbl+DDRV9dcnOO5u4JE/HvZ48hL2b/2Quhgj7Wg7vZFlJ6ez/jvJfGckjtB4Az87dysmjy/T6e3hrxOy3UHw+Q/53ke/Be6nNCnRCI6tH5B67Hfucu3eHn4ybTVzQwH+TF3TNja6XtDDgP39cvU3jsPTRtUSYw1Z7l3K9ufMvmue2s9yvGY3VbXvcexkDzacX0r/A489Oov4CGfD/tcs9vyy3r3Gpkf72SvYvsjm/mLdFEz8zEWsXDWL2FHO1zfYOizsLN3Lbz9qo/s8xpqtE6eR8eijzJ3pf81W68cN7Cyvc3+JPSqESfemk7VsDpP89D95dQVPxTTw0qYdxpfVfsMAj/576me9ZlcIE7+Ql5+fgOXFCkd4AqFxSWT9dCkp8UbvBw433DzXK+1vnwHXNHUEL79q8jiW/ta/PVlD7rLdtAApz71CxvkdbKlsoPmUzTiWc5aSnz2tT7jFMMfK4Aw+gXFpFG+dT5+CY5/QwZdnKAnO8/iXPgHi5NlpLF9iJtrjPPMeq0Ol23jjQIcxVjHT+MHqFaQ6xsrF3knzmzuoqj7C8UDWEvZZexhCYxJIyfghGQ9M8DmWQ3K+geKMCixJi9n+/Czf3+2P85iaQpi+4FFSws7QPXk+qXFHKM9+kVprDMmPzMEcA6d/X8cb+zsInb2CTXlJhAItW/PI3dVN7H1pZHw3iuCuDur31HCoAxKXlVCQFkJ7UxP1b5RRfSaJ7CUzuP2OJBLjgjn99nM8Xd5G6OQU/jE9gfCuE9TurKf53ASyfrGW9DiwHSjjiRcsRNyTxtwHYwjvOkH1jnqOW6eRu+OZfmdKGLxf/UzFe76JkqzNNIZNIeP7s7gz4izH36qm+uMezKteIff+YJ+A2MVvcNwBJgiflsbjD0bS1fj/qDzQSfj9z1C6ahrBzmC9awIp6SlMj4HTjjbRj6zl5Szj1qf2qudYua2N4PFJ/ODhGUT85T12vtlE+2gz+VuXMv39fsaxq+8ax0afugkf2wN/479P1mMWLG9tp3x/FOl5KUwaNwXzZKtPcOzsU/jEFP7x4QRCOxx9srvHzzgmRwgfa4OYWfxjegIcredX75ygO9LPNe505gSW/bVUljcRMW8p6XdFMnl2AuHWJkp+tJlGaxTJmf+AOeY8x6v3Un3sLOEe56U3W+sRmj7t6bvx3AdUlVpoj5/PxpI0YumgOiefypYQJqWlkz5ljOuctznbmDyPoY1bp6WR8d0wupztPP6e2g5WsOyFBqwxxphF04nlrRoaTwaTvKaYnJn9hcfG35hDY0PoNcUwNyOFSTiuja4oMn5RRGa8sS7xoP3t8nccQ2hwjWUqvU1N/Oe/b6d8PyRnL8QcEcnk2Wd5zSs4HvQcvNRAcWYFlrEJpGekMCmil9OH6/n1b9roHpvCum2LmOS3Pwn0egXH1gNlPP2Che6YJLIyZhB93vn+QzyOXWDHabifISIiIiIiIiIiIteTgYLjL6/E9Us3g6Jo7xK5CCJGOn60HvMNjTHCwFvajnDYyGAg+DbHusV1fOhcyDP0O9QmraTsDvc+Gw8+T0rt86TUvsKLjs03jcP17D0FEEJqur/QuIPqnByeLW+g2RkaA5zvpLmmgtzypr7th+vTap5dVkbtx47fYbfRsr+ClavqOO3RzHqgjGXZZex0BZ0ANqwfN1H5Qgl7fdYA7qH5pTwey6mg+kNHaIxxw8Dx/dt5tp/+f9JYxrM5FTR69Ke9aTfPrq2n27vxUJ2qp3B5EZVNRmgMYG1tYkvOCwGuYXx12D7eTW5WEeX7vY7lyRPG+rcb/K9/21yez4+L6o3QGMexrHmRn1a29Wk39LHycOZdqg8aP05KT/EfKA3ENIFkzym6z7dRlZPDs+VNrtAYwHaug+aaMn68pIxD/t6sY6zK9xuhMYC14wjlOZtp9Gxv76A6J4+CbU2O0Jg+x/KnpUfd1w5Aa41x7H9zwhUaA1g7TlC96TnKHe992E6foR2IvSvB95oeRPT3niE/axbmefNJnQwtb26j9tw0lm8tJCdzFubZs0hfXcirOdPo3reD6haAozTu6yZ89lL+dXUayY4psfM3LcVsguajJ4xp3JPMJI4DQuOYPttMYlwwnLdQubWNiLR8SjcsInW2GfO8RRRUriUjso3K1yzYgOZGC9bxaeS+MN/VZt2aFKJNJzj0fp+j6yGQfvXj/fdotMaQuSaPzHlmzLPnkLXhGdIjg2k+cqTveAYoet5aXn1hPsmzZ5G+pojiBVF0v7vDuBZONtHYCslPrWW58zivKWD5PWA9/AHtAOctVFW1QdJiXi1dQfocM8mZK9hYNIfoC0doOOAORL3H0b8euGcFm3z6tI2dLRA+2cz0O0KBSBJnmzFP9hOyn6ljy7Y2Qu9bwaYSY/ySM1ewcetizLjHz9CDbdpSNjnGOXVZPuszY+BUE//Z3+dBZALmJOOmrej/acY8O4FwoPnVrTSeiyHzF0WO83IOWRuKWZcW5XFe+gqOm2ZM2e583DeB0/9uoX2smfx/NgLh7poKKltCSF5dzLplc9zn/NPToGU3Vfs8R7+Hb3y/gI2r04xjuLqQvNnAwQ9oBqCNnaUNdN+9mFdfMsbMPCeNnNIScpJ6aCyvpZ+uuljtU8gpyydrnuPaWD+feDo59B/GjUIB9bef4+hmXJ/GeIcSn+SvTWDnYPe+JppNMWStd/R59izSc9aS/0gUnDvK8Y7+x7UP+1HeeNEx3b7jd5nnLaJgaz7p43yP3WDHaXifISIiIiIiIiIiIjeOr01wfGv4LN5JWunxcFYWA5zhwz8C48byDceWL3q7fUNjp1EfcMoZDo4MdayHHMzGg4fxjLXGf9P9+2r/9wryosK4JSrk63NQA2LDUt1ghINxqTzkZypg27s7qHR8Mxv/yFq2V1ewa08F298sovjpFBIjhhpL9aO1jZbQaSwvf4Vd1SXkznZUYLVs5w3XrOBtVJdajOA2NImcylfYtaeCXdWv8PovVpB137fwKk7G+psyCt4x1m8OnphGwVbHPm+9xMY1aUzvp//t+y20jDOTW/4Ku95aS2ac44kPLTR1OVslGWs/7qlg154VJLt3H5i1k/ZzIZifLmL7ngpeX202vjC3t1FZZQTZxjqPxmvnz3buGENWqfP3GQ/PSmLPfV5eNMi6xvajvPbzGlrswNgpZJcYx2V7ZR4ZjpS2+92tvOFYYtjT6Y5OiJ/Dum0V7Nq2lGRHWd/p2gaOu1oNfaw8teypN14r1EzGHD9hlWO9Vs/H62scxxGIz1xqVDk6HN/6L+xsAQhherbjPH6rhIIFRhUnXRZeeu2oewenPmP1ChsfcRxX+xHq33WvDXy8tNC4TkwTyHihhO17Kti1s5Dl9xrncXtNheP3A9hofHW3ceyJIdNx7HfteYXXy/NZnpbAuP4+3wLV0Uk7MGoYrzPpOwke/+rg0IFOiAmHIxYs+9yPZmsw0a5QZgpZla/wal5S36B6zAQmxQJfWPsPWpvew2IPJvbWMzR5vL5lfwe9Ee4ALnxcFJysp7KyiXar49WmLuLlt15i+Uz/1/EV9WtcFNF0sLd0N4daHDeQmBLIqnyF13O8Xi8gCTyU7jjfHOJTzMTSQePBToiMItoEjZVl1B/rxGYHCCHlhQpeL0kjFqD5Ayx2SJ47q2817cSFvPzWS+Tc765c7TuO/Ynhocy+lbnxDyYbYdth43NzMN0H3uM4YczN8KrwjZhF6gMhcPAAFteNIzA9uW+76DsmAB20tHtsHNRRLPt7IGkOD3lc5xDMpIwUJtFJw+/63sjiXw+HNhRS2RJFxvNLmR4KcJam35+AyGTS7+tbCRz6QCqpoWD53QGP8yaGv0vu+3kbOz4GaKP9JND6HpZTEPsNaN7veQ0dwTombODQ3OmemZg9D9r4GG4HWk7+aRj9vUIBnIPh33uG19/yXVYh/q6/AaxYL/Td3q8Pm2i0gvlhI9B3GZXAQ+kJcKqB//jYY/uAx2m4nyEiIiIiIiIiIiI3jpsi42z7rJyNo4BLHrNyBw301kcQFOS9DUaMreeJ2ufZ66+qkG8wJ2kl70x9wPuJr7eTtexyVDaaM1J9plYG6DrnUV973oozkw8OjSJ+ziIKsqe5n78Spglk/fMzpMQEgykMc2aqq8q08ffO9LKbLldoa6PX2RlTMKHxSaSvXkFqny+q26iucuwbaiZv/XwSxzm+HB4VQuzM+eT31//QJHJLlhrrHo6awCxXMNBBy2Bf8gcgflE+uXOM6YtD70tzhx/73+OQV9trwfZuLbXnjJ+nP76C1InGcQmOnELmM85ptnuo/Y2f5Hismfx/XmhM8R1hZtYMx3ZrN12Oqtyhj5WH801U7zFCq+jvz2V6INOHWpsof9ERVMelsdIZ8GJM21z9jqMS855HyZk3gWATMCqMxCVPkOFYk7O7rsHvsXePVTCx95td52XXWceHyXkL1XXG68c+spTMe8KMUHFMDCmPpxiBH51Yfu+cRtzz2ID1vLNKNNiYqnpZPpn3up8fltgY4oFe13gM15+M8OtkA1uKyij2fJQ3cRpo6Tjjbm63Ye04waF9DVSXbqYgu8BYM/XMWTzech+nT/4JsHGoyuv1i8qoPgbQQXsHxGcsJn28jUNvbmblI0+yMDOfgtI6Y43VwQyjX8SnsjwtBuuRGgp/8hQLH36KlXnbqD3cOcwQLhKfiTTi4rgTOPXfZ2CMmczsKYSfsrBlVR4LH36SJ3I2U72vzVXtfvpkBxBDvHFSXQUTiPW+Dp1h2x8DCV6h94seII5YP30aFxUOnKHL4xRhGDcz+OrBaoXoO+7wDfAjI4kGTp8ZPPhur/oXCvf1EL8oh8yJzq1WrF8AcTGOG888mMIYF+l73owa6H9JHDdxtP+mwuf8Lt9/1ji/jVyzfwMes6H390oM5Ry0Wc/SftiC5e3dbPn5czxWZAHOcsrzfBiI1YqVMGLv8BllwsdF+r7WgMfpCj9DREREREREREREbgADfVX5NXCGvbXP80SbbxXwraOctcf+TGecs5TR9oVHlXEwwVFhHtNSP0/KsU/4wvU8MHqqewrrm0DzW45pHkNnkTrb94tZgOh7Z7iCspaaIh7LeIpnf76b2sNt7mmfr4bYGZg9K8cc4QUAn/7JMV31FMzOiirrEbZkP8lj2YVsqbLQ4pw22VPHUQ6dcvz8dzMDCyCdZvStXHJX875Ef1lz4GJIvs+zQi2GWOd5ZzdCsmut+YgzEI4h8dte62vGT3FU3gF/bO0zVTgA98xwP9/Hnzjt6vsQx8pDd12tYxroBB76ft8KTf96sGza6tgnhsx/mt+3Oq35KM7VkOOnJnitezqB6UmOftodFYJ9eI+VW3uHI+1xVOABtFfls+D7i92P5TXGFMOe7YnC7JpGu4Pq1TkszHyO4so6mp2VrVcqJobbTdD+XycGeD0bjWuf5Imc7Rwf7FqevcKnwtv1WGVcEO01RTzxsDHOhSU72Hugk9CkWZi9w1K/fKvp3Y+1pMcAoVPIKn2F7aV5ZD8yjXhTJ8012ynIzqHEY4pmb8PvVwiJywrZvrOQguw0zHHBdB2rp3xNHsuK/E/jPlyhY4yzMjYtj1d3llCcN5+UyVHYWpuoLHqOx3JqaLcDvRe9d712TP7/JnxttNawsaqN8NkrKPBcC/0aSV7jfV67H8uv9EaRL1Mg56D1KJXLnmThIzmsXFPGlp0NHL8QxdyZgVTBX0PD/AwRERERERERERG5UXjnqTesL/7yG3eY63qUszEqjGBnCPzfLXzsTEBCJrLeUSXo446p3O2c5rr3LLsB7viha1rq8mlh3BLleJz7FfNqnyel1j2NdcTI6e7X+jo7b6H2t8YXpfEPzyGxv0qd8WkUFM1neoTj3709HG+qoXzNczyWkUPh220DBFNXid1V58z0VQXkPhjjqjKzdpygflsZuUueZOGybRzyLKty7QfxMYOmRNeJi3DFVaIBcP0OPxWHOKrUrsgQx8qljb3VxrqzobNTSQmgH9b9W9my33EuZz5FRp+paz3fK9x+h29AFPENn5U1hybQ8fII4qIz81m3aArhjuvOdq4Ny5vbKfjJUyxcVERtyxVeVaZpmJOApkbq+6vusx7A0mSj2xThvtnGR5RxLnz48cDrsJ6pY0vpUWx3L2LjTmMq+Jcr15K7bJaj4rp/EZGRQAfHjwX2noPHTyE16xnWbXuF7ZVLSQ7tofGtA/7XHr+CfrmMiSFx3nxyS0p4fWcJOfeF0L2vFovr/O3mlNe5fLrD390fZ/pUmgPQ2sonwLgoj+nYR4URPzuN5UWFvL7zFYofiYGWWvZ+CNF/09+0zkfY8vCTrKwcYM1mv/z06WQHnwGJd3lfSP6NujUEaKXdp09wqrMb+BbRvpfdFQohNBROf/qp79+fM2c4DcTGfMv7GTdrEyU/201LbBprV3lNsU0oobcCrR343Edid1S4jo/xO0OHX5FhRAPHjwZWwT10V7m/gwjkHGx+dTPVJ6NId0zb//q2EjYWrSDjHq+blAYTGkooZ2n/1GeU6T51xrjpahjn1pA+Q0RERERERERERG4gX5vgmBG3uMNc18Or0tj0Pn/43Lkw3i18J3alT3Xw/LuW8c433dXIbef+nSCAzz93VRaP/+Yyd+hsuoVRUWFk/q9v46prvOwxJfbXWHddvVElaZrGgnkDf/MaOjWN/G3G+qu5i5KYNNYRgNnPcqj8X3jtsPceV4FnGBcZhjO3xhSFOaeQ7TtfYuMLC0mfGkWoM3w7WU/hC3V+v/w99Rf3erTXI/eUwpFEBBCWXj3+qmzPuEMgU7+J4uCGM1aH69l7CiCKuRkBrCNrbaJ8k6P6My6NlYNUDn72qW+g1+4K+UYOOtXpYMw5zvWK/TwclbmGECZl5vHqzpd4ecNSsu6bQKjzUHcdpfxn22gONJD2KxjzD+YQzQnK1+6m3bui2N5J47/uwGIPIeXR+11rQ/uawN/dHwVnGql2hPNO1v2beSz9SYrf7YGPP+U4MP3/pBDrUdlv+7CBhv6Ca4fg+2ZiNoHlLUdVrZO9g53LF7MwezctdFC96ikWrup7zgRHRBIx0EwCV9Cv9jcLeezhQmo9240KI9pzXfTQUELp4ZMWj88XewcN9f5CwhPU7vE8/3o4tKOedhIwzwzDdqCCJzKeotzz89QUzLj/4REqJ34Hswka9zrWpnew7m/A0mvjzruGWtHp3SebYyYKo0+BCJ85g0mcZe9Oryrsrgbj5qRp05h8hdeVL8eMBk11/LrVc7uN4zvrOU4I0+/p57PA3kH1zzbTaJ1AlvfsBACEkfTdBP/n/G9rqbXC9OlDOM4Tv8uscXC6thpLnwPUg2X9UyzI2EyjY9mA4bnK/R3MoOdgCJ8c64HxSaQ4p+3H+P+FhkY/Sx8MZGoSyaF+Pht6T/Dr6hMwdgqJgd3fYMzuMJzPEBERERERERERkRvI1yc4DsgI3vrwmMfU0zD+m0YVsfPxo9tucT9pPcwTnzq+sjztUa3sCJ372+8vf21y/fy1ZT/Kr95wVHU+kIo5oC9NjfVXzZkrWFf1Evn3OyuHevivY75hXB/2DhoaB2njxbavgUbHz/FTEnwDxDEhxN4zh6yiIl4vX8gk5/Zjn7orIx3T9QJY9zVw6LzzievMeQuNBxw/xyUwyXs8fIKNKxc/xRkkdND8kdcUnR9/5Aoto++ZcuWVaoGMFQBnqd3mCCMmpzDXOUd6vwaZotppcgKJjh9bPjzhNcVwG8edWUbkFP5nP1nTgBzrCQNYGg8MbQrjUSFETzaTvnotr+94hhRn6aP1BJ8M7ZLxNXEh+cumEN5Sw8pH8ymurMOyz0JjVQUFWXmUHOwhflE+y+/1ubr6iM94lOSxPTSuz2FlSR2WfQ1UlxTydFET1rg0MmeHwMQEEk3QuCGf8rctRpv1+TyxuoEur3sPQkNDoLWRN9620NxqM9b2zZwArTWszCqiqs5YE7X4qQKqWkMwL0klnhjM343Edmw7q1ZVUF1nwVJXw5ZVL1J9JoSUjFn+w+8h9Mtb7MwZjLOfoDynkC1VDY73/RxFNWcJvz+NWRGOYGssHN9aSElVA5a6GkqeKmCvaYKf6yaEv+wpYOX6Ghr31VG56lnH+rqLSY2E4LuTSAruofZ591jVVhbx7EtHIT6dh6bhPlZNFTyds43afRYaK4uMsYhfyA9mev/OwbVUOfvUQNWaXArq3H3CVVF8hOqtDViO+bkBJ3IOyxdNwLp/s7tPVZtZuaQCCxPIWtbP2AzFrSGEAofe2k7jvhN0A4lPLCF5bAdVP8kzjv2+OipX5fJsTSfhs5eQMdX7RTDW0i4ppLIFYmfPIvpTC5Z9fR/NrTbC0xaTFd9D4/pcni11nPPr83li0xGIn0/WA4GF6oYJpGebCbc2UbzIcR7W1bBl1bMU7+8hfsF8ksd67zM0AffXz3EcskHPwRgmTQ2BkzU8t2Y3jfssWN7eRkF2LluavT5rBuuPaQo/eMZMeGsNK5dtNq77t7dRsKSQ6lMhJD893/33ZFDD/AwRERERERERERG5gdxkwTEEhfyWJzymle6X7RNKD9a7p7k2vc/PfhfAftbDPNHy9T+stn1GFRLEkPHwFO+n+zj99osUlDrWXnVW/JzvpMsjP4iOjHL89C3indU/7Sdo7gLOt1G9qoCqPlVhflyy8oWjKtJ6pIaickeaZ0og9UFnmneE8uWbqd53gtNW550ANro7Ot3TlUaGub/8NSUxd45znV0LhXnbOHTS0bK3h/YDuyl0/p4vmfWCox9dR6l+/peuNXIT587yCZxi73DWw3fQuOco1iuqRDWEzzS7pic/9Npmah13VtjOHKVqU52xrrEpgYz0QNYY9meIYwXQUs+vjwGEkJwxUBWsYdApqp0iZpDsDJEO76Dk7TbjXO49S/PWV9npqChNzJjrCoCHZPwsUp2v31RBwUtNtDtvUjjfQ/vhOipXbeeQa4cOan9eSOXbR2k/556C1Xaq0yN0vjqV57FpeZSWLyV9Ihyr2U5xURklVQc4GZlE9voSigep0AYgNImcf8sn+75v0bVvO8VFFVTu72Tc/Ut5eUOaEdZHppC7fj7myE5qy8so3rCNX59KIGtDMfkPhED7CVocx2TSP8zHPK6bxvIyCnYeBSA2cy2vrk4hccwJdm4qo7i8lmamkLV+HTkzjWs4esEaNj6dxLhTB6jcVEbxpt1YrAlkrV/Xf/g9hH75GD+HdSWLSY7sxFJV4Xjf55m8KI9NOdOMm1lMU8guWUFqvBXLtgqKS2s5PXkpG346g1u9X49pZP/zo8S21FBStJ3q1lCSn17LOucYjJnG8i3PkDERmt82xqr87VZC71/KyxvmuD4XnMdqvLWB8qIySt5uZdz9K3i1xN0mcDFkrnb2qYJf/zGM1Lwid5+A8Pvnkx5no3lXBcUvWXzXPAdiM9fwcp5Hn6qOQGIaBeVrSe/vuhyKiJksSIuBY3WUFP2Shg7Hebklj+yZYzj0ZgXFRdvZ2xFG6tNr2ZTnPf20UyftfzQ+M9rf3UZxUZnP4/Xfd4IphvQNReSmxWD9reOcb7KR9MgzvFriOOeHIHTmUjYVLSQ5rpv6cse5e8r3WA9boP31dxyHYbBzcNKyAnIfnIDtwxpKisoo2XGU0NlLebn0URKB4//l+L+xAPoTOnMpm9YvJDn0KG9sKqO4vIGWyFks/0Wx67MhUMP6DBEREREREREREbmBBPX29vqdV/ny5ctcvnwZu91Ob28vFy5cYMlnO6j49uPeTQFY/NFr3puuvah5vDkhjluBL7obeOSPgc53bMPW2cOlxOXU3+6drFzg8LEifnpyNKO+cYsxTbWLsd//Tf45c32+UXbs96cQbnFOw+xt2P29+vobx8CcpXZVDuXHgHuXsv05s281r4fTVfn8eJufb3Od4uez0eOL6dO7nuPHW30j+vCJExj1cRungeQ1FeTMBGii5PubXZXFPkxhpKxex3LXl8ODtCeE5DVeXybbO6jOyaeyvwVaZ69gV16S4x8er99nu3+DHhuAuPm8vCWNaOBQ0WIK93k3cAufvcJ/4HGmnoIl/qcuDvhYOrjbQ3tNEc+VHvWt8gIgBHNOAbkPOm4KOFlD7rLdRoWw17Fxv68YskoLSR9PAP3xHavmTU9RUNcD49Io3jp/4BDX2kTJ4s2OamP/4hcVuoPR1noKf7aNQ/1MCRt+72I2rJnlWnPY/3sa4DgM8vpgJn/PUowV1DuoXp5P5QA3U/Tpu8g1YHx+0ff8FhEREREREREREZHr2uKPXmPr7Y8yevRoRo0ahclkIigoiKCgoBu84vi/dzCv9nlSap9nXvNQpocOJjgqjFvayklx7O9+bOBn58K4xSc0du+38aD3Ph779RcacyX9vc4cqeFXjqrO1PSBQ2OA6AV5FD+dQuL4MNf6tBBM6PgEUrLzeN2r+ip6wT+xbtE0oh3V3sFjJ5D89FpKV/mrwEsgZVEK5olhhHtOzzwmjEn3zSe/tNgjNAZI4qnSFWTdN4Foz7EaE8akpDRyy0t8K5BMMaSXlDjeQ4j7/Y4JY9J9C1mXPXA4fK24q+GDCZ+YRNaaIkr9hcYYVZP5pUv7rBF8NcSm5bGpZHHf1x0VQuzUFHLLS9yh8bAMcazO1LPrt0Yl4KT0lIFDY4Aj7w0YGvuISyF/y1qWpyV4rHVrnMfpeUWUPucOjYclLoX8yiJy0xKI9Xi/wWNjSHxwPvmlSxyhMUAMc5/PN/riuV7uqBBip84KvBJYRERERERERERERETE4cauOJZh628cB2fDsvZJig8SWFWnXFX9VrEKLZV55L7ZCaZp5O54JsB1t0VkuFRxLCIiIiIiIiIiInLj+fpWHMuX78y7VB80fjQvSlNoLNcH+1Hq93QCEL1ggUJjERERERERERERERGRIVJwLEMTOYd1eyrYtaeC3PsHm6Ra5EtimkL2m8Z5+XLWBO9nReQaiM4sZNceVRuLiIiIiIiIiIiIfF0oOBYRERERERERERERERERuckpOBa5gUzPM6pqVeUnIiIiIiIiIiIiIiIiV5OCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm9xVC45vGTHSe5NcpzRWIiIiIiIiIiIiIiIiIuLpqgXHibfe7r1JrlMaKxERERERERERERERERHxdNWC4/TIuxkzIth7s1xnxowIJj3ybu/NIiIiIiIiIiIiIiIiInITu2rB8fjREay+43vMGBunqZCvQ7eMGMmMsXGsvuN7jB8d4f20iIiIiIiIiIiIiIiIiNzEgnp7ey97bwS4fPkyly9fxm6309vby4ULF1jy2Q4qvv24d1MREREREREREREREREREbnOLf7oNbbe/iijR49m1KhRmEwmgoKCCAoKCqzi2NlYRERERERERERERERERERuXP1lv4MGx84d/e0sIiIiIiIiIiIiIiIiIiI3jv7y336DY8/GQUFBjBgxgrGm0XRdPO/dVERERERERERERERERERErmNdF88z1jSaESNG9MmBnT/3Gxw7ORuPGDGCxJAY/nD2E+8mIiIiIiIiIiIiIiIiIiJyHfvD2U9IDIlxBccBVxw7OUNjk8nEnNsms6vzEK0X/uzdTERERERERERERERERERErkOtF/7Mrs5DzLltMiaTqU/VsVNQb2/v5T5bvFy+fJlLly5x8eJFLl68yO8+/5jXzhxkQdR0/jbsTiJGjvHeRUREREREREREREREREREvmJdF8/zh7OfsKvzEI9H3svf3zaRkSNHMnLkSJ/wOKDg2Bke2+12Ll68yInzndR9fozmng7O2S947yIiIiIiIiIiIiIiIiIiIl+xsabRJIbEMOe2ySSMiWLkyJF9Ko6HFBzjJzx2Pi5dusSlS5dcz4uIiIiIiIiIiIiIiIiIyFfLGQqPGDHCtSyx8+EvNCbQ4BiP8NgZIHsGxgqORURERERERERERERERESuD85g2DtA9tzmLeDgGEd47PyvZ1is0FhERERERERERERERERE5PrhDIe9w2J/oTHA/wf0qsMboSHotwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here there are other subscriptions to try:\n", + "![image-2.png](attachment:image-2.png)\n", + "![image-3.png](attachment:image-3.png)\n", + "![image-4.png](attachment:image-4.png)\n", + "![image-5.png](attachment:image-5.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "
\n", + "Go now to the ETSI MEC Sandbox console to see the callbacks
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## (4) Gracefully terminating a MEC Application\n", + "\n", + "\n", + "\n", + "
\n", + "When the MEC application instance is notified to gracefully terminate, it provides to the MEC platform \n", + "that the application has completed its application level related terminate/stop action
\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZIAAAAxCAYAAABApFyMAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABlPSURBVHhe7d1/cNR1nufxZ6dDL+GHO2bwtmGImWErsjWZO4GCMQNDsTkYQU9QRglnXBbBIxaRMreKg7hWLFNZIys6EzaEMjMGuaxwCTowcYZfA5vJZWCicIA3xtrAHSeExa4Do4PBpDrp7vvj+/12f/vbP9LhN8nrUdUF+X6+/enP5/P9dKry7ne/vy6/3x9CRERERERERERERCSBNOcBERERERERERERERE7BZJFREREREREREREJClXvNIWoZBxyPpXRERERERERERERIYGl8sV9S/OQHIoFAo/gsFg+P8iIiIiIiIiIiIiMvi5XC5cLhdpaWnh/7tcrkgg2QoaBwIB+vr6CAQCBAIBgsEgVruIiIiIiIiIiIiIDD5W9nFaWhputxu32016ejputzs6kBwMBgkEAvj9fjhxguHvvkvasWPg9zv7FBEREREREREREZHByOMhOHkyPY88AnfdhcfjMYLJfr8/ZJWy8Pv9BP/1Xxnx938PS5bAzJm4MjKcXYmIiIiIiIiIiIjIIBTq7oaWFqir4+t/+AfS/uqv8Hg8kUByX18fPT09jHj1VdK+9z1c997r7ENEREREREREREREhoDQvn0EP/6Yr59/nuHDh5OGrT5yMBg0ylnMnOl8noiIiIiIiIiIiIgMFTNnknbsGMFgkFAoZASSMYPJwWAQ/H6VsxAREREREREREREZwlwZGeD3xwaSMYPJIiIiIiIiIiIiIiLYYsYuv98fCgaD9Pb2cunSJW4vKMBVX+883/DWW9DSYhRclsviysgwSoc88YSzSUREREREREREROSmEVq8mC8aGhg5cmR0RnJSb71FaN8+BZGvUKi7m9C+fUZQXkREREREREREROQWkHoguaXFeUSuhNZTREREREREREREbhEpB5KViXx1aT1FRERERERERETkVpFyIFlEREREREREREREhiYFkkVEREREREREREQkKQWSRURERERERERERCQpBZJFREREREREREREJCkFkkXk5nL+OJUvPsvcwmLyq447W2VI8NGwppj8wqeZ/2I9R792tg9ca1Ux+YWRR/kR5xnX0ZHN5Bdu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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Delete the userLocationEventSubscription\n", + "The subscription DELETE method in MEC013 is used to cancel the existing ```zoneLocationEventSubscription```.\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# GET subscriptions list\n", + "url = mec013_path + \"/subscriptions/zones\"\n", + "payload_dic = {}\n", + "method = \"GET\"\n", + "\n", + "response = requests.request(\n", + " method, url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscription_list = response.json()['notificationSubscriptionList']['subscription']\n", + "len(subscription_list)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "payload_dic = {}\n", + "method = \"DELETE\"\n", + "\n", + "for subscription in response.json()['notificationSubscriptionList']['subscription']:\n", + " url = subscription['href']\n", + " response = requests.request(\n", + " method, url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + " print(\"Status Code\", response.status_code)\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "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IiIiIiIiIiIiGi0YSCYiIiIiIiIiIiIiV1kHkm/gyhZDxrIjIiIiIiIiIiKiq1nWgeSC21LqU5Qllh0RERERERERERFdzbIOJJdMGcC9gRRn1w7CDV7g3kAKJVMG1E1EREREREREREREVw1PIpEYSKVS6O/vRzwex7Ptfrw8K6mmIyIiIiIiIiIiIqLryPJDXqyfHoPP57POSPZ4PPJ/iYiIiIiIiIiIiOg6pseMjUCyx+NBTk4Ocr3ABU5IJiIiIiIiIiIiIrpuXUgCuV4gJydHxI6hBZH1QPLkW5PoOpv10slEREREREREREREdI3pOitixZZAMiAiy2PGjMEDgThaPvHgvWgOZyYTERERERERERERXUcuJIH3ojlo+cSDBwJxjBkzRgSTE4nEAACkUikkk0kkEgn0fOnB4agPJ7/wIsFgMhEREREREREREdF1IdcLTL41iQcCceR/ewC5ubnwer1mIHlgYAADAwNIJpO4ePEikskkkskkUqkU9O1EREREREREREREdO0xbqqXkwOv1wuv14sxY8aIILLHYwaSIQWTBwYGkEqljL+JiIiIiIiIiIiI6Non309P/zstkKzTg8cMIhMRERERERERERFdX/TZyfq/APD/AQDitv4VcafzAAAAAElFTkSuQmCC" + }, + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZcAAAAyCAYAAAAgGeVmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACCiSURBVHhe7d19VFRnnifwb1VBjfg2HcbslkQkYzdmTkhO1CNK49gui/FtGqIx4EiWNpIOORKPzCqJknHJCYcJOqLTOAgndAJx6OAIcbCxW9GWoRk6NBFWyEmTM8i0GyxDaldCXEVhC6rYP+5L3Xvr1hugonw/59QR6z5167nP89xbdX/13N812O32URARERERERERERERBcCofYKIiIiIiIiIiIiIyBeDt5nLo6PCIulfIiIiIiIiIiIiIpoaDAaD6l8t3eDy6Oio/HA6nfLfRERERERERERERPToMxgMMBgMMBqN8t/aILNbcFkKJDscDoyMjMDhcMDhcMDpdMrLiYiIiIiIiIiIiOjRIwWQjUYjTCYTTCYTgoKCYDKZ3ALMbsFlp9MJh8MBu90OXLmCaZ98AmN7O2C3K4sRERERERERERER0aPKbIZz8WIMvfQSsHAhzGYzTCYTjEbXbfxUwWUpDYbdbofz3/8d0//2b4HUVGDlShhCQuQXEREREREREREREdGja3RwEGhqAioqcPfv/g7Gv/gLmM1mOU0G9ILLIyMjGBoawvQDB2B85hkY1qxRrpOIiIiIiIiIiIiIpojRCxfg/MMfcHffPkybNg1BQUGu1Bmqgoqb+Bnb24GVK5WLiYiIiIiIiIiIiGgqWbkSxvZ2OJ1OOX4sUQWXoUiNAbudqTCIiIiIiIiIiIiIpjBDSAhgt8vBZSW34DLEADMRERERERERERERETzEjFU5l51OJ4aHh3Hnzh08lpwMw8mT6tKSDz8EmpqEpM40JoaQECHtyKuvahcRERERERERERERTRqjW7bgu6oqzJgxA8HBwTAahTnLujOXvfrwQ4xeuMDA8jiNDg5i9MIFIVBPRERERERERERE9JAJPLjc1KR9hsaD7UlEREREREREREQPoYCDy5yxPLHYnkRERERERERERPQwCji4TERERERERERERETE4DIRERERERERERERBYzBZSIiIiIiIiIiIiIKGIPLRERERERERERERBQwBpeJJr1BdNYcwytpGYhLyUVVr3Y5PfTayhGXkoG4tBzknLdplxJ55uhD0/vvIWFbBuJSytGiXU5Tgq0mF3EpGVj7+nso+WLsNwqW1qN95LVpS3piQ9XeDKTXTMHjWG8d0lMmftvd+qSoQ1tkEupAnmoMBfDdpa38Hh/LxjhGe+uQHsh2EBEREU0h9zW4bPrZr2FubHR//PofYPKnXGMjzI3/hCAAwFsIVv3fXWDr8fD49T8gyK/1PBj23x1DXEoGdp67pV10z/l/wjOClvf3IS5lF3aeC/DLfCD6O1B28D0kvO+pHgLbufewNiUDm9/vhF278EHpqcXWlAwk/LxLuwT235VhZ3Uneoa0S+4X4URMG2zw3ufjMYLu8x9jz+6CyX8SJwWF3R7eTkCl9tQpM9SHpuOHUOI+DNzcq31f2q8DPvG+l4Y6UZiRgbht7+G0ts385dfx4T4dqwLl5fjQ/clR5DRex8CwdsnE4Hi4t+PB7XNUfngJrolBzLi9ddDWyn77Oqr+vhRnb2oWBCI8EScqi9EgPXZHo/5IIAFm/7QU3avPkAfD9tklIDYaaOlw65exstXkYmt1OPIV/ZGP0knXbuq+7EBeSimuJeW4xlDBMlzM0vnMm+RsNbm6+xkRERERqd3X4LJzVPuMaOYSmBRBWo/lAACjcOj+6y6w9Xgy6ud6HoTr+EV1J2CKwpa42dqFU8/VdlR8fu+CLPfOCJqq62DDHGxa85R2IS53dAIIQXzGAZyvzEFymLbEo6YP7f/6KS7b7moXPLqWbkdD5VF8tGkegEG0f+nrVJb7fsAeyeODDe2X+wBYkJZ3FA2V2xGjKUEePLTjQZ9lUw4aKg4gNzYEcHTh8n9oS4zD0u04kWRBfTWDbJ514INqYPWLizHfWosPJigQ/9XXNiB2sWq/jnkxEZHN7Z5/fHjQem24BgtWL7e4ngtbh7RYGy5+NhlGkAXJB4tRuklRP3+ErUPplPgORkRERBS4+xpcljj/5z+i54c/FB//iKEBAIiAseItL+Wkx8sI9IJP7+s5iG/l585AOM/8CgPSc2sy5eCx9/U8AF80ouYGYPnxC1g5Tbvw3rNsEmel7I7WLtIIQszrB9BQeRRF6wP8Mn8PWNa/jfOVxTj1ehTM2oUPQn8jTl4GsCQB/y1Cu1DyDFb/5ewHVF/hRKyhshgnkoT+i5RmJO1cpC08tSzdLs7MSkc8ACBanGE2lhPQIESsWIJI7dN6HvC+f99Ni0JmcTEajr+NjQG3ayAm17EK8PP4EL4Mzy94kNfQ3GeP0HiQP0cLEoV9X541PIYfCkyzsTLmGe2zE8ISHg5Yv8FX8jPqtAeeZzXrl7PV5CK7GUCzchauflldmqtGPM2sF2bU1imuvilHi+p91DPEVTPJtTNWpRnjKRnuV560taM+fBl+FLYIq2OB+hbFzGIxzUOVYt3ytkmpFmpc26PcliefsADaQHLYOpRK48MtVYOwbcL6pfQPda7tVWyTftsoeNretnLE7S1HnngFzof/pOnLMAvmwz2QHLPTFdDVzlrXmyF8Ta+9pNfqbA889V9vHdJTypEnvi6vTZkWw0sbtZVja7UNsNZi69462Dy0tVsdxXJ5RYq6eJpp7lY34WnVNipfq+oT7T7ioT46aUCU7d9SlIH0onL11RCe+t5b3YiIiGhKeyDBZRiMmGs2i49q3C1rgxMAQueq0mOoy0mPIAQcS/G6HiMek58zwCC+5E+k54JNrkbyup777RbO/sunGMACbHl+nuJ5bZ67DCRkvIfCz/rkEsKX71xUNNcjZ/cuoVxabsBl/KH6EurpBNDRh8snj+GV18X3SclQn+RovkzHpexCQtYxVHUJYX35ZOJIq1C+uVT3i6/b5cd6X4pvdqHqYA4SUsUyaTnIqenCgPQLg3hCm3exEyX792BtSgbiUnch/XgXBuSVjMDW/DF2Zii3x8O2A+g8fRadCMHGtdEBB4+17RuXtg873+9Av7f6bstBTs1Vub4T09diqoe9tWg6J+WHzsDazGM4a1UUu9mFqoO5Yn5YbV9LYzcXJVZhnSVZUhnFyY2P8eB3XQD0f16LnCyxXcSH6mRNlc9WGg+utvNX/2cnsUd+n1yU+Lg02D6ifUbJ077vq331UxxIY0gbyLEEf4uqgzlCnbXjYVhol81i27r1kchX+wrvXY4Whw1n8/eJbVyAMrkvtccz9/fwNX79PT5o9yW9/XXgyzrXe6RkIGH3MVR9qUhLore/PcDjw8CXdW7tr3dMnBzjQezrog7A2ojsDLGN361Fp3wRw8M1HnTrsm0MedUdfWg6XuD6jNxbi25tGa2JvqQqbC4iYcW1XojHWEXag4JEXDvi3hfeylk25SA/FkBsuvgjpeey7jqQd8SKHQWutB3d1ec8z+RtvgRkCj/67QhvRXZKO1bLPwq2okzq27ZyVQqK/PBabFUGvrNqMX+39J7hKMlyHVtbWloRGbMIFo8zi1tR8nWiIs2IcttsKGmZK/yoUJAIVOfK+4VlU7pYZ3EsagPefuiuvoSFBeL2Q7lNAJprcSVJ3N7YVmTL61dv74kkqLYX1lYgSVjnqz/R9uUi7N8dje5q/eCwfzy0V1s5sq2utC354bXI8av/WnHtCWFs7V+qfB+BbhuJM/YRnogTB9dB/fOSZrzq9Gk9xPoXJCKyudRLG6jrZqvJVWxjDnZYS12B8ELFGCxIxLUjUp/4qo933c1AWmUxGg6ug8XLWPdcNyIiIprqHkxwWcWAmZ/YhODyzFDVEuOSN9xzHGtmN/tjsq1nQvT8FhVdwMy4BGxUN5ubgZvXcbqwWPMl04ayolNosolRrCEbThceRVlPoGXG6xbO5uVgzy870XPba0RNYQQDvZ0oyftntEzkCfRQJw7tLUTJ532uYPJQH5qqC7HtuDrXaX3ZMVRdHRRyNjuEHMFVUrt8eQqvFX2Kzpt+bM9QK042DAKRG5D6rHYhgJutuNgBYPp0zNIu0zN0C52NpdhXq/6yr6rvcB+aqgvw979T1m+C+tpah5wKV35o+41OHDrZKua2voWzhYUo+dw2wZeiexgPXusC9J8vwOaDdWjqFdvFzSDqC3LU+WyHhLbb/Sv/T6b6zxdgc2EjLivfx9O4DZ0FC4Cetkvo8dRGHvf9iW3fppPHUPJ5n1BnzXjorMhHTuN19HvJA+67fSVW/DL/EA59IQblhq6iovg3voNnKhM0fr2wXy7Htrxa13sAGLB1oiTvXRR+qSp6344P9p5LuGgFMD3EPfB8sxHv5NX60f7+uW/jwdqI7LyTaBFzBg901SHvzHVtKR8m03jQ1GW4D02/OIUmL22lJnxG5py/6vqM9HT8AIDQ2bAA+Kyl1fU5NtF6O3DRGo00Ka2Ap5QH/pZDgGWxCPsVV4fYrJpfDbViE8WyFswPhyLNhAULw13FWlpaEZm0Xp4xrgoSt7WjHtFYLQUml25XzC7vwMVmRRqIsEVYHd6Ki6pgogU7XhSv9Fm6HjvC1dsWnyQGL8PWIU0189l15VBDQSIirUL+dd0fxz1RbH9yUrR6JnR4In4qblPMi4mItF7Cv/W6b69lUyLiodwmRVvoka/qKUZ+LFB/RP/HIM+8tJci7YhqNnRLqyqFSMxO5dVVmjQdWt7aSE9vBy5aFevU1lFZ/zAL5ssv1KOsmw3/1mJzjQexPt2KPN5yehrVDHZf9fFBmXrF41j3XTciIiKauiZBcBmQpwsDQpDZm1GvCZD9N9nWE6DLFxo95OBchP3iF/qGymI0HD+AzGcBwIYr2i/1EfE4XHRUyNO4PARAH1oua74i+lPGi5id0qwXD1/qvzwn3LxsVhTezD+M83LdFZcEh61DqXKbyjKRHArAYcW1/62TniM23VVWkbbBrZxGf+M5nL0NIGIdSsuK0VB5FKd2r4AFQH9Ds/pEwzQHG7MOoKHyMN58FgD60HtDXDZ0V5ilOGseklMz8dEHR9FQqZ/fr7+hEU0OYOXzq6D+jUCcfZtRjvr/F4QlG1chSrVcILWv8DiKU9uE8dD9taaPZkXhzYKjQl7fF4R6NHX8QV1mnH0tCX16Mz46XoyGd1YJJyA3vkU/AMCOATGgErE0Ebnv5uJ8hbKvpbGbgx3hEE7OpJlpyjQTPsaDkue6dKGi+iqAEKx87W2cOe5anzyrqec3+OBzAI+vQH6R0Idndi7CTADdTf6eTF3H6TPi+6TliNsrbZ+OacuxZb0FZmsdXtmmH0DwvO/7at8AzfM8Hm4PCTOLQxesQma21H7KVCB+tK/MhpauP0PqO4fRcDwd8SYAN6y4NgSdMeGFh/Hrtt97OD54P1bdwsUzregHELnpbaFdjx9AftwcAIM4/dtOdfF7fnwQZvCuza5Dd/AcbPyrZZrlAIZGcBtCXeKTtqO0UHifMaeyuV/jwdqFltB4FH1QjPOZQl1tX/WIAemHdDzMegqZ+UfRUJElHqu+xTfCgci3q79BRReA6dIxXJFCQ8+CFUh9djYGPitHQqr+jOsx6f0G3QjH/DDpb8Vs2pQMZDfrfO74Wy7QspqbzOZ4jQL6y4ZrVqhm28Zl1aJbnK1ts1qB8Ll4UvsyALaaWtRrrrQpsSqCgAAgtR3gCnLLLFioSO/y5BN6Y07x2edzJqxapHJ9YXPVYyfc4pqRqwiCetveQEn7Un6sDSWF/s689tBeS7fLN5hUz4oW+k+1rQHw2kZ6lPvDhLLhilUKxouPI61iShoLkg+KM6ulZXIai4mrj+e+91Y3IiIimuomR3A5OUxIhzHwrWpCjm6O45SDihL+mWzrGbf+epQ1DALPrUWyNgen4xY6z5W6Lrnetg+FX2jKiCKXrcCS0CDANBuR8/9Uuxjws8x42K7+EQMAIv7rC9gQoTP7DuJML+UlwWmFqPL3xDwAV/54FQCwMmEDIqcBQBBCl0YJQV3tDLDlLyJzyWwAIQjVTile8tcoSopCxPB1VFUU4pWf7nK/jBwAHF2oOHUVeDweqT8cW77UgS/rkbd/n5jGYxc2a2ZYy55dgQ1hQUJe3+/rR2Umpq8t2JIWj4hgALP+VDPbeg6Sd6dj44IQfNNWi5x3crA2dRe2HqxTXPbuB7/Hg5e62K4L7/n4KrwaNw8zg5ULRTe+FU7abnyK7J3CeyUUdQSYEqMPvf0A8Aw2rLbArMr7o2cYt+96mdHqbd+fqPYVeRsPMa9kYcdzc2DvaURhvpA2JGHvx2iSAqj+tK+CZf1PkPZUCBC8CPsritFQ+QbiA8w35K2+42dF538AQBRSN84T+jF4NmIW/7mw2KmZKj4Zjg+WeBzIXIGo6TdRX12O9MxdbilxAuGtfSd2PMxB6mubETUdMC8XA797V+h/Nnjhrb7jF9h4iFy3BRsjggDTdPzZDNUi3/pvCcehRdIx3AcHMGAfww7vQ0tLqyvgFDYXkXJ+ecVD+8OFv+UCLdt2DiVWV9nSJP3PtMAIAUz5fgLyQ/iRxD3ntESczSmlEJAeBYpZwAAgpxQRXnNNNdla/eP/V3JAvQN5erN9fc6EVVMF6Hu/UV8VYrW5gr29NlwT//S8vb7ZanJ1fxj1GDT3SdNeilnRJ5IsqD9Sjhax//R/jPDNaxvpUaWJmUjCbHq38ST/SKyYyV6ZjnirmBZkAuvjue991Y2IiIimskkRXA5KWAIDAOeV30M1D1gvx3HQGKo82dYzTt2/aRRycG5YgZmaZbban2FnRQfscW/hfGUxzhdmIc3HFAx7fxfOXhK+WM+aPl27GPCzzFhYwuYCAHr+9Zc426N/uXTL+znIOX8NP9h2EA2VR3Emfzvi3abpKdz+v2O6HPjJ+UL+2qYzZ9E9BAAj6G9uRzsCSEsBAAhB1KY38FFZMc5/8DbyX3gKoUM2nC78CGfFS70BwP77izh9F4ha9zwi3YKO4glEcTrip4/g8kmdfJK9ddiddwr1w7EoKitGQ1kuSl9YoC2lNmTD2d8KM5ZnTg/RLgXuYV8DAB5fhMy8wzhfcRSn3n0ZGywjsH1ei33/rBcUv4tvdVIHBDwe9FgsQpvfaMSHDYq0F0rTpwv7V/gqHJZmfUoPt/yLngTjT0wAYEV3z4iYb/cczno6Abx5CdWNfbCHrxNmXGsCK972fcD/9u2+8kcMYAS2hlIc+ky1yI3ueJi+AMl7c3Gm4ijO5Kcj9ekQDFg/RU5xozAz3J/2VYhaqMkdPQ669VUa0/HhPyNiHgB0ouL0ddgdAIZvoanF+76kbyKOD8IM3vP56xDp7MPpU826swFDl7+MovePCseG16IR4exDU/UxfKhJ4zG5xsOf42kfh7FA6NZX6YGPBx+krxjWa0KqnOE+NJ1u9hz86mpERdcIZi7fjjMV+jPiA9ZWjuxmZeoGIe2DnKtYnEnvNkva33IIsCygCNbaUFUt5s8ep5gYde5mIWe2mNN26WJ1Wgjp5m4dQjoPtxQRYYuwWpWWQPF32zmUKFMYQJEGo7cOZc1AfIyQu3h1rE2d6xjSTGnxPbU3z2trR72iLABFigexrZQpEBQB8JZ/qUV3+DL8KMx9e1Xv6YNl+TJENmtz8Xbgg2qbnJdafaNCIUCvpsyF7WovW436xn+W8HD5R4+YGHU6C21Zr7y1kR5t/+r06dhY8KMYi2rWe0uRNENZebNGoezCcGB+uMVHfcTAu5y+ogMXm6V16PA01nu91Y2IiIimOj+mwUw8IXfxG5pnv8LQrioEmVw5MvTLDcB57K8wUiX9/0kENzaqi6AHzlU/kSeb+rce3yZqPeMy1IqKX/UBj6/DBp0cnLcHhRlL3TXvYW2Ndqlad3Uu4qoVT8yKRtKq2YonvJWxoWqvdOM1UXMp4poBIBr5ldsRgw7kpZSqTnTk9Uk3SHnueaQ+3oqKG504lL0Hh+SS0jpGcOcOAIygvmgP6osUK9OSTsK/OImE1JPC36qbBfmob+wKLDl5Epd76pCeVqcoCER6SEuhx1aTK9xhXMsUglnybMzr+EV1J2CKwpY4dZurfG8RVj8L1OudCAzdES57t9bhlW3q+rqRt1VkmofUdeq0CoH0tVxWbl9/uI8HycwZyul8QWJf3kJV3i4Iu5YFOwpykBwWwHjwKgob18/B2V/1oenn76Hp564l8bvFS/WfXoXUiEaU9DRiT6b6GBOZlCMEbdrKXTcGAwC0IjulVVHfKGyIC8HZizaUZe9CmaKkrruDuA0gMmaZMONayce+70/7Wp56CjNhw8DnHyMh5WNtMZVAxoNshhiQ96d9/eHWvsKl5yXKPhB5rq/I6/HBve20x6rV/2UBSo5fdT+26uxL3kzk8cEcsQyrw+rQrZ05Db22k8zALHF343iYhOPBWoutKbWuz6XnYrFxVitO+3OcB4C7QtqV+BXRmOn2o4Sf5DpIhBRFrjQnFiQfTMeVFEUbx6ajYZMFUIWZvJUTg4FHShFnFdrUW1mVpeuxIzxX7HsLduxOROSRS7jWC8SMJy3A0u04kZSLrSkZ4hMW7ChQpG0qSER6VgbixKXxu4vxo/+Vi5LYRJ1ApBCIK6k+h5bdABCN+V/nIi5FWBq/W2zPXqFsPGoRl1IKiGNZ2ididhbjxBPKOoljsFL6gXMRfppkwVb5MzkRO8KBK67SiIwFylIykC29VvmZHRuOK/I2RSNfsV719irbwp22L0srLchT9qW4zaXKHM7Vpa7Py6RoqH9Bj8ZqlCIuRRhPcnttykH+1xmK8SnUywK9/hO3p9f3fuOpjSzLlyGyuhZbU75BfoEwGULgPrbVfTp2FrdtdPWLdgwiNh0NS+G9PlI+7SzXsWVHkgX1X0sr0dL2vbf2V44ZIiIimsru/7RbPQNt+O6HL2NEEVj2xuEzzfGoWxYDPb7X45+JWo8/5BycSRt0c8JFbtyO5AVieglTECKeWoW0VR6+9kknnsEhiFq+GUUHtyNGeym6P2XGwzQPaflZyFw+D6G66w1CfMoWrLSIv4OI9UjWC64t+WscXuvrkmsvQlchP+9lxEvtB2Dm9+Zh485cFP3YQxv6Q+yH3EOvY6W0jV80ouYGYPnxC67nArVgA95UbO9MywIkb1oBt2wJUPSjXJe3FIECTZl71dc6zLPmIT41Ez9/STlzdQ6SMxR9rhLAePAhMiUbRamLEPU9vfeBcLL2P4SxOeYxBSAq9b8j87k58j4ZuSodh/UCJj742vf1uLXv04l4d61FCDqZghDxXCLeXKuuizni+4gKVaSo8Wc8TJuDJWu340RmtPw63+07wXyN33EeH0LX/g0+em0RomaJ22MKQuiCFfr7UiDu1fHBTRBmLliEzP1ZSJMOEhwPk288aJmewo63N2Pl4+L7TLNgY+YbSJuITBA65JzUqocyf7ZEc38HxY3Tkg8qZ0x7KqdIbyBfCeKlrIoyNUAOkpeuQ6luHbU3ddP+X1tX7fZr1qnJ979/qVjeQz2FdbkCsqsV90jQ/qCy8EXX+2pnm7v1iebKGdXyneuQfFCz/ifWu+rtdtXNYkWba4LHqu1VtIXqZobK57z0pds2K5fnIHnTdtdrxfUnK7ZL+Vr1vSbUfaRuK7GOYdrx4d7vHttIboPtiHFbj3ob5Tp6KKftc0CvrEC9jV7uQaIaex7q4/Y6sW3F12r3Effy3trf8w8ORERENLUY7Ha7HBp1Op0YHh7GnTt38FhyMgwnxdk8CqNbtmif8pvDMYz/oxeJNZjwn4JN8rmgx3IAAANmBQdjpsGJ7+wj0L/huhGPmYMQ7Nd6lM9J6xReL52H+lcf7fP+02tnXY4uFGYU4rRjBQ6//zKWjHFWkjRTSjvTS8mfMjRWt3D23X041LUAmUVZ2OgjpUNLUQaym0MQn/kO9i/3PItRlzTTz8sMY/b1ZDEC269+hq2VV937YoL2/UfRozd+Azk+iLOGe+chLe8tpEbcp8DtJPbojYcAOG7hclkB9jT0BTYjnO6ttnLEHYF4NZZGbx3Ssy5hdYF7gHH8hOPDxRj9faGlKAPZ8PzdYGrw3kZEREREpDa6ZQu+q6rCjBkzEBwcDKNRmLN8X2cum0zB7jmLzWbMVQSWvZYzmzHXLAVyjXjMbZn0EALD/q1HSVqnK7CMMa3n3rD//iJO3waiNicwuPQw6/ktKrqAmXEJPgJHgoXfXwBgEPWF+xCnd3Mfevi1lSMuZRe2Vgo3lZwfrj7J5b4/hQR0fLDgBz8IARzXUZa9y5UflqYcW00u4lL3YU9DHwALFk54oJKIiIiIiEjffQ0u0/iY//INNFQWo2h9gLNXaXKJSMSJymKcec2/vJyha15B7qqxX75ND5Fpc7AyKQt/o5lxyH1/Cgnw+LDk5Tew47k5Y8+vS48U86x52JiZcQ9mwdKY6aWRkHhIizAxdNI/KOimQ5hyvLcREREREfnnvqbFIH167UxEREREREREREQ0GUyKtBhERERERERERERE9GhgcJmIiIiIiIiIiIiIAhZwcNkQEqJ9isaB7UlEREREREREREQPo4CDy1i5UvsMjQfbk4iIiIiIiIiIiB5CgQeXX30VhjVrOON2nAwhITCsWQO8+qp2EREREREREREREdGkZ7Db7aPSf5xOJ4aHh3Hnzh08lpwMw8mT6tJERERERERERERENKWMbtmC76qqMGPGDAQHB8NoFOYs685cNhgM2qeIiIiIiIiIiIiIaIrSixm7BZcNBoMQeTabMTo4qF1MRERERERERERERFPE6OAgYDbDaDS6BZhVwWWDwSAHl52LFwNNTcrFRERERERERERERDSVNDXBuXixHFxWBpjdZi4bjUYEBQVh6KWXgIoKjF64wBnMRERERERERERERFPI6OAgRi9cACoqMPTSSwgKCpJzLUtUN/SDeFM/h8MBu90OXLmCaZ98AmN7O2C3K4sRERERERERERER0aPKbIZz8WJhEvLChTCbzTCZTKoAs1tweXR0FKOjo3A4HBgZGYHD4YDD4YDT6ZSXExEREREREREREdGjR0p7YTQaYTKZYDKZEBQUBJPJ5JYWwy24DEWAeXR0FE6nU/6biIiIiIiIiIiIiB59yvvzSX9rb+inG1yWSAFlBpaJiIiIiIiIiIiIphYpmKwNKku8BpeJiIiIiIiIiIiIiPSob+9HREREREREREREROSH/w+etyq95nyavQAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete the AppTermination subscription\n", + "\n", + "The following method in the MEC011 Application Support can be used to delete the ```AppTerminationNotificationSubscription```.\n", + "![image-3.png](attachment:image-3.png)\n", + "\n", + "\n", + "But first ywe need to retrieve the subscription ID:\n", + "![image-2.png](attachment:image-2.png)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {}\n", + "method = \"GET\"\n", + "\n", + "response = requests.request(\n", + " method, url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + "print(\"Status Code\", response.status_code) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(response.json()['_links']['subscriptions'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "for subscription in response.json()['_links']['subscriptions']:\n", + " print(\"Subscription Type \" + subscription['subscriptionType'] + \" \" +\n", + " \"Subscription ID \" + re.findall('/([\\w\\-\\.]+)', subscription['href'])[8])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "app_subscription_list = response.json()['_links']['subscriptions']\n", + "payload_dic = {}\n", + "method = \"DELETE\"\n", + "\n", + "for subscription in app_subscription_list:\n", + " response = requests.request(\n", + " method, \n", + " subscription['href'] , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + " print(\"Subscription Type \" + subscription['subscriptionType'] + \" \" +\n", + " \"Subscription ID \" + re.findall('/([\\w\\-\\.]+)', subscription['href'])[8])\n", + " print(\"Status Code\", response.status_code) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/demo6/python/notebook/.ipynb_checkpoints/MEC application-checkpoint.ipynb b/examples/demo6/python/notebook/.ipynb_checkpoints/MEC application-checkpoint.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3477891716cdf831054ec00cbbc5a75b92aee5aa --- /dev/null +++ b/examples/demo6/python/notebook/.ipynb_checkpoints/MEC application-checkpoint.ipynb @@ -0,0 +1,4804 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to develop a MEC application using the MEC Sandbox HTTP REST API\n", + "This tutorial introduces the step by step procedure to create a basic MEC application following ETSI MEC standards.\n", + "It uses the ETSI MEC Sandbox simulator.\n", + "\n", + "
\n", + " Note: These source code examples are simplified and ignore return codes and error checks to a large extent. We do this to highlight how to use the MEC Sandbox API and the different MEC satndards and reduce unrelated code.\n", + "A real-world application will of course properly check every return value and exit correctly at the first serious error.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What is a MEC application\n", + "\n", + "See [The Wiki MEC web site](https://www.etsi.org/technologies/multi-access-edge-computing)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The basics of developing a MEC application\n", + "\n", + "The developement of a MEC application follows a strict process in order to access the ETSI MEC services and provides valuable services to the customers.\n", + "Mainly, this process can be split in several steps:\n", + "1. Global initializations (constant, variables...)\n", + "2. Create a new instance of a MEC Sandbox (Note that using an existing one could be a solution too (see Annex A))\n", + "3. Activate a network scenario in order to access the ETSI MEC services\n", + "4. Create a new application identifier\n", + "5. Register our MEC application and subscribe to service termination (see MEC 011)\n", + "6. Use MEC services in order to provide valuable services to the customers\n", + " 6.1. Apply MEC services required subscriptions (e.g. MEC 013 location subscription)\n", + "7. Terminate the MEC application\n", + " 7.1. Remove MEC services subscriptions\n", + " 7.2. Deactivate the current network scenario\n", + " 7.3. Delete the instance of the MEC Sandbox\n", + "8. Release all the MEC application resources\n", + "\n", + "**Note:** Several application identifier can be created to address several MEC application.\n", + "\n", + "\n", + "## Use the MEC Sandbox HTTP REST API models and code\n", + "\n", + "The MEC sandbox provides a piece of code (the python sub) that shall be used to develop the MEC application and interact with the MEC Sandbox. This piece of code mainly contains swagger models to serialize/deserialize JSON data structures and HTTP REST API call functions.\n", + "The openApi file is availabe [here](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/blob/STF678_Task1_2_3_4/go-apps/meep-sandbox-api/api/swagger.yaml) and the [Swagger editor](https://editor-next.swagger.io/) is used to generate the python stub.\n", + "\n", + "The project architecture is describe [here](images/project_arch.jpg).\n", + "\n", + "The sandbox_api folder contains the python implementation of the HTTP REST API definitions introduced by the openApi [file](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/blob/STF678_Task1_2_3_4/go-apps/meep-sandbox-api/api/swagger.yaml).\n", + "The model folder contains the python implementation of the data type definitions introduced by the openApi [file](https://labs.etsi.org/rep/mec/etsi-mec-sandbox/-/blob/STF678_Task1_2_3_4/go-apps/meep-sandbox-api/api/swagger.yaml).\n", + "\n", + "
\n", + " Note: The sub-paragraph 'Putting everything together' is a specific paragraph where all the newly features introduced in the main paragraph are put together to create an executable block of code. It is possible to skip this block of code by removing the comment character (#) on first line of this block of code.\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before going to create our MEC application skeleton, the following steps shall be done:\n", + "1) Change the working directory (see the project architecture)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import os\n", + "os.chdir(os.path.join(os.getcwd(), '../mecapp'))\n", + "print(os.getcwd())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2) Apply the python imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import division # Import floating-point division (1/4=0.25) instead of Euclidian division (1/4=0)\n", + "\n", + "import os\n", + "import sys\n", + "import re\n", + "import logging\n", + "import threading\n", + "import time\n", + "import json\n", + "import uuid\n", + "\n", + "import pprint\n", + "\n", + "import six\n", + "\n", + "import swagger_client\n", + "from swagger_client.rest import ApiException\n", + "\n", + "from http import HTTPStatus\n", + "from http.server import BaseHTTPRequestHandler, HTTPServer\n", + "\n", + "try:\n", + " import urllib3\n", + "except ImportError:\n", + " raise ImportError('Swagger python client requires urllib3.')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3) Initialize of the global constants (cell 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MEC_SANDBOX_URL = 'https://mec-platform2.etsi.org' # MEC Sandbox host/base URL\n", + "MEC_SANDBOX_API_URL = 'https://mec-platform2.etsi.org/sandbox-api/v1' # MEC Sandbox API host/base URL\n", + "PROVIDER = 'Jupyter2024' # Login provider value - To skip authorization: 'github'\n", + "MEC_PLTF = 'mep1' # MEC plateform name. Linked to the network scenario\n", + "LOGGER_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # Logging format\n", + "STABLE_TIME_OUT = 10 # Timer to wait for MEC Sndbox reaches its stable state (K8S pods in running state)\n", + "LOGIN_TIMEOUT = 3 #30 # Timer to wait for user to authorize from GITHUB\n", + "LISTENER_IP = '0.0.0.0' # Listener IPv4 address for notification callback calls\n", + "LISTENER_PORT = 31111 # Listener IPv4 port for notification callback calls. Default: 36001\n", + "CALLBACK_URI = 'http://mec-platform2.etsi.org:31111/sandbox/v1'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4) Setup the logger instance and the HTTP REST API (cell 4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize the logger\n", + "logger = logging.getLogger(__name__)\n", + "logger.setLevel(logging.DEBUG)\n", + "logging.basicConfig(filename='/tmp/' + time.strftime('%Y%m%d-%H%M%S') + '.log')\n", + "l = logging.StreamHandler()\n", + "l.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))\n", + "logger.addHandler(l)\n", + "\n", + "# Setup the HTTP REST API configuration to be used to send request to MEC Sandbox API \n", + "configuration = swagger_client.Configuration()\n", + "configuration.host = MEC_SANDBOX_API_URL\n", + "configuration.verify_ssl = True\n", + "configuration.debug = True\n", + "configuration.logger_format = LOGGER_FORMAT\n", + "# Create an instance of ApiClient\n", + "sandbox_api = swagger_client.ApiClient(configuration, 'Content-Type', 'application/json')\n", + "\n", + "# Setup the HTTP REST API configuration to be used to send request to MEC Services\n", + "configuration1 = swagger_client.Configuration()\n", + "configuration1.host = MEC_SANDBOX_URL\n", + "configuration1.verify_ssl = True\n", + "configuration1.debug = True\n", + "configuration1.logger_format = LOGGER_FORMAT\n", + "# Create an instance of ApiClient\n", + "service_api = swagger_client.ApiClient(configuration1, 'Content-Type', 'application/json')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5) Setup the global variables (cell 5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialize the global variables\n", + "nw_scenarios = [] # The list of available network scenarios\n", + "nw_scenario_idx = -1 # The network scenario idx to activate (deactivate)\n", + "app_inst_id = None # The requested application instance identifier\n", + "got_notification = False # Set to true if a POST notification is received" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create our first MEC application\n", + "\n", + "The first step to develop a MEC application is to create the application skeleton which contains the minimum steps below:\n", + " \n", + "- Login to instanciate a MEC Sandbox\n", + "- Logout to delete a existing MEC Sandbox" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### First steps: the login/logout\n", + "\n", + "Here is the first squeleton with the following sequence:\n", + "- Login\n", + "- Print sandbox identifier\n", + "- Logout\n", + "- Check that logout is effective\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The login function\n", + "\n", + "To log to the MEC Sandbox, \n", + "the login process is done in two step. In step 1, a user code is requested to GITHUB. In step 2, the user has to enter this user code to https://github.com/login/device and proceed to the authorization.\n", + "Please, pay attention to the log '=======================> DO AUTHORIZATION WITH CODE :' which indicates you the user code to use for the authorization.\n", + "\n", + "It uses the HTTP POST request with the URL 'POST /sandbox-sandbox_api/v1/login?provide=github' (see PROVIDER constant).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Login\n", + "def process_login() -> str:\n", + " \"\"\"\n", + " Authenticate and create a new MEC Sandbox instance.\n", + " :return: The sandbox instance identifier on success, None otherwise\n", + " \"\"\" \n", + " global PROVIDER, logger\n", + "\n", + " logger.debug('>>> process_login')\n", + "\n", + " try:\n", + " auth = swagger_client.AuthorizationApi(sandbox_api)\n", + " oauth = auth.login(PROVIDER, async_req = False)\n", + " logger.debug('process_login (step1): oauth: ' + str(oauth))\n", + " # Wait for the MEC Sandbox is running\n", + " logger.debug('=======================> DO AUTHORIZATION WITH CODE : ' + oauth.user_code)\n", + " logger.debug('=======================> DO AUTHORIZATION HERE : ' + oauth.verification_uri)\n", + " if oauth.verification_uri == \"\":\n", + " time.sleep(LOGIN_TIMEOUT) # Skip scecurity, wait for a few seconds\n", + " else:\n", + " time.sleep(10 * LOGIN_TIMEOUT) # Wait for Authirization from user side\n", + " namespace = auth.get_namespace(oauth.user_code)\n", + " logger.debug('process_login (step2): result: ' + str(namespace))\n", + " return namespace.sandbox_name\n", + " except ApiException as e:\n", + " logger.error('Exception when calling AuthorizationApi->login: %s\\n' % e)\n", + "\n", + " return None\n", + " # End of function process_login\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The logout function\n", + "\n", + "It uses the HTTP POST request with the URL 'POST /sandbox-sandbox_api/v1/logout?sandbox_name={sandbox_name}'.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Logout\n", + "def process_logout(sandbox_name: str) -> int:\n", + " \"\"\"\n", + " Delete the specified MEC Sandbox instance.\n", + " :param sandbox_name: The MEC Sandbox to delete\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> process_logout: sandbox=' + sandbox_name)\n", + "\n", + " try:\n", + " auth = swagger_client.AuthorizationApi(sandbox_api)\n", + " result = auth.logout(sandbox_name, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling AuthorizationApi->logout: %s\\n' % e)\n", + " return -1\n", + " # End of function process_logout\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let put in action our Login/Logout functions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the skeleton of our MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Logout\n", + " - Check that logout is effective\n", + " This skeleton will be the bas of the next sprint in order to achieve a full implementation of a MEC application\n", + " \"\"\" \n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " return\n", + "\n", + " # Print sandbox identifier\n", + " logger.info('Sandbox created: ' + sandbox)\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Logout\n", + " process_logout(sandbox)\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective, verify on the MEC Sandbox server that the MEC Sandbox is removed (kubectl get pods -A)')\n", + " \n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Second step: Retrieve the list of network scenarios\n", + "\n", + "Let's go futhur and see how we can retrieve the list of the network scenarios available in order to activate one of them and access the MEC services exposed such as MEC 013 or MEC 030.\n", + "\n", + "The sequence will be:\n", + "- Login\n", + "- Print sandbox identifier\n", + "- Print available network scenarios\n", + "- Logout\n", + "- Check that logout is effective\n", + "\n", + "The login and logout functions are described in cell 3 and 4.\n", + "\n", + "To retrieve the list of the network scenarios, let's create a new function called 'get_network_scenarios'. It uses the HTTP GET request with the URL '/sandbox-sandbox_api/v1/sandboxNetworkScenarios?sandbox_name={sandbox_name}'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_network_scenarios(sandbox_name: str) -> list:\n", + " \"\"\"\n", + " Retrieve the list of the available network scenarios.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: The list of the available network scenarios on success, None otherwise\n", + " \"\"\"\n", + " global PROVIDER, logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> get_network_scenarios: sandbox=' + sandbox_name)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxNetworkScenariosApi(sandbox_api)\n", + " result = nw.sandbox_network_scenarios_get(sandbox_name, async_req = False) # noqa: E501\n", + " logger.debug('get_network_scenarios: result: ' + str(result))\n", + " return result\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxNetworkScenariosApi->sandbox_network_scenarios_get: %s\\n' % e)\n", + "\n", + " return None\n", + " # End of function get_network_scenarios\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Putting everything together\n", + "\n", + "Here the logic is:\n", + "- Login\n", + "- Print sandbox identifier\n", + "- Print available network scenarios\n", + "- Logout\n", + "- Check that logout is effective\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the first sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Logout\n", + " - Check that logout is effective\n", + " \"\"\" \n", + " global logger, nw_scenarios \n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " logger.error('Failed to instanciate a MEC Sandbox')\n", + " return\n", + "\n", + " # Print sandbox identifier\n", + " logger.info('Sandbox created: ' + sandbox)\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Print available network scenarios\n", + " nw_scenarios = get_network_scenarios(sandbox)\n", + " if nw_scenarios is None:\n", + " logger.error('Failed to retrieve the list of network scenarios')\n", + " elif len(nw_scenarios) != 0:\n", + " logger.info('nw_scenarios: %s', str(type(nw_scenarios[0])))\n", + " logger.info('nw_scenarios: %s', str(nw_scenarios))\n", + " else:\n", + " logger.info('nw_scenarios: No scenario available')\n", + "\n", + " # Logout\n", + " process_logout(sandbox)\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective, verify on the MEC Sandbox server that the MEC Sandbox is removed (kubectl get pods -A)')\n", + " \n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Third step: Activate and deactivate a network scenario\n", + "\n", + "Having a list of network scenarion, the next step is to actvate (and deactivate) a network scenario. This step is mandatory to create a new application instance id and access the MEC services.\n", + "\n", + "In this section, we will arbitrary activate the network scenario called '4g-5g-macro-v2x', which is at the index 0 of the nw_scenarios. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def select_network_scenario_based_on_criteria(criterias_list: list) -> int:\n", + " \"\"\"\n", + " Select the network scenario to activate based of the provided list of criterias.\n", + " :param criterias_list: The list of criterias to select the correct network scenario\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + "\n", + " return 0 # The index of the '4g-5g-macro-v2x' network scenario - Hard coded" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The activate function\n", + "\n", + "The process to activate a scenario is based on an HTTP POST request with the URL '/sandboxNetworkScenarios/{sandbox_name}?network_scenario_id={network_scenario_id}'.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def activate_network_scenario(sandbox_name: str) -> int:\n", + " \"\"\"\n", + " Activate the specified network scenario.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('>>> activate_network_scenario: ' + sandbox_name)\n", + "\n", + " nw_scenario_idx = select_network_scenario_based_on_criteria([])\n", + " if nw_scenario_idx == -1:\n", + " logger.error('activate_network_scenario: Failed to select a network scenarion')\n", + " return -1\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxNetworkScenariosApi(sandbox_api)\n", + " nw.sandbox_network_scenario_post(sandbox_name, nw_scenarios[nw_scenario_idx].id, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxNetworkScenariosApi->activate_network_scenario: %s\\n' % e)\n", + "\n", + " return -1\n", + " # End of function activate_network_scenario\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The deactivate function\n", + "\n", + "The process to deactivate a scenario is based on an HTTP DELETE request with the URL '/sandboxNetworkScenarios/{sandbox_name}?network_scenario_id={network_scenario_id}'.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def deactivate_network_scenario(sandbox: str) -> int:\n", + " \"\"\"\n", + " Deactivate the current network scenario.\n", + " :param sandbox: The MEC Sandbox instance to use\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('>>> deactivate_network_scenario: ' + sandbox)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxNetworkScenariosApi(sandbox_api)\n", + " nw.sandbox_network_scenario_delete(sandbox, nw_scenarios[nw_scenario_idx].id, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxNetworkScenariosApi->deactivate_network_scenario: %s\\n' % e)\n", + "\n", + " return -1\n", + " # End of function deactivate_network_scenario\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together\n", + "\n", + "Now, it is time to create the second iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Login\n", + "- Print sandbox identifier\n", + "- Print available network scenarios\n", + "- Activate a network scenario\n", + "- Check that the network scenario is activated and the MEC services are running\n", + "- Deactivate a network scenario\n", + "- Logout\n", + "- Check that logout is effective\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Check that the network scenario is activated and the MEC services are running\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " - Check that logout is effective\n", + " \"\"\" \n", + " global logger, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " logger.error('Failed to instanciate a MEC Sandbox')\n", + " return\n", + "\n", + " # Print sandbox identifier\n", + " logger.info('Sandbox created: ' + sandbox)\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Print available network scenarios\n", + " nw_scenarios = get_network_scenarios(sandbox)\n", + " if nw_scenarios is None:\n", + " logger.error('Failed to retrieve the list of network scenarios')\n", + " elif len(nw_scenarios) != 0:\n", + " logger.info('nw_scenarios: %s', str(type(nw_scenarios[0])))\n", + " logger.info('nw_scenarios: %s', str(nw_scenarios))\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + " else:\n", + " logger.info('nw_scenarios: No scenario available')\n", + "\n", + " # Activate a network scenario based on a list of criterias (hard coded!!!)\n", + " if activate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to activate network scenario')\n", + " else:\n", + " logger.info('Network scenario activated: ' + nw_scenarios[nw_scenario_idx].id)\n", + " # Wait for the MEC services are running\n", + " time.sleep(2 * STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Check that the network scenario is activated and the MEC services are running \n", + " logger.info('To check that the network scenario is activated, verify on the MEC Sandbox server that the MEC services are running (kubectl get pods -A)')\n", + " time.sleep(30) # Sleep for 30 seconds\n", + "\n", + " # Deactivate a network scenario based on a list of criterias (hard coded!!!)\n", + " if deactivate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to deactivate network scenario')\n", + " else:\n", + " logger.info('Network scenario deactivated: ' + nw_scenarios[nw_scenario_idx].id)\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(2 * STABLE_TIME_OUT)\n", + "\n", + " # Logout\n", + " process_logout(sandbox)\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective, verify on the MEC Sandbox server that the MEC Sandbox is removed (kubectl get pods -A)')\n", + " \n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Fourth step: Create and delete an appliction instance id\n", + "\n", + "To enable our MEC application to be part of the activated network scenario, we need to request the MEC sandbox to create a new application instance identifier. Our MEC application will use this identifier to register to the MEC Sandbox according to MEC 011.\n", + "\n", + "Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.2 MEC application start-up\n", + "\n", + "#### The appliction instance id creation function\n", + "\n", + "It is like the MEC application was instanciated by the MEC platform and it is executed locally.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def request_application_instance_id(sandbox_name: str) -> swagger_client.models.ApplicationInfo:\n", + " \"\"\"\n", + " Request the creation of a new MEC application instance identifier.\n", + " It is like the MEC application was instanciated by the MEC platform and it is executed locally.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: The MEC application instance identifier on success, None otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.2 MEC application start-up\n", + " \"\"\"\n", + " global MEC_PLTF, logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> request_application_instance_id: ' + sandbox_name)\n", + "\n", + " # Create a instance of our MEC application\n", + " try:\n", + " a = swagger_client.models.ApplicationInfo(id=str(uuid.uuid4()), name='JupyterMecApp', node_name=MEC_PLTF, type='USER') # noqa: E501\n", + " nw = swagger_client.SandboxAppInstancesApi(sandbox_api)\n", + " result = nw.sandbox_app_instances_post(a, sandbox_name, async_req = False) # noqa: E501\n", + " logger.debug('request_application_instance_id: result: ' + str(result))\n", + " return result\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxAppInstancesApi->sandbox_app_instances_post: %s\\n' % e)\n", + "\n", + " return None\n", + " # End of function request_application_instance_id" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The appliction instance id deletion function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def delete_application_instance_id(sandbox_name: str, app_inst_id: str) -> int:\n", + " \"\"\"\n", + " Request the deletion of a MEC application.\n", + " :param sandbox: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> delete_application_instance_id: ' + sandbox_name)\n", + " logger.debug('>>> delete_application_instance_id: ' + app_inst_id)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxAppInstancesApi(sandbox_api)\n", + " nw.sandbox_app_instances_delete(sandbox_name, app_inst_id, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxAppInstancesApi->sandbox_app_instances_delete: %s\\n' % e)\n", + "\n", + " return -1\n", + " # End of function deletet_application_instance_id" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Getting the list of applications" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_applications_list(sandbox_name: str) -> list:\n", + " \"\"\"\n", + " Request the list of the MEC application available on the MEC Platform.\n", + " :param sandbox: The MEC Sandbox instance to use\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> get_applications_list: ' + sandbox_name)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxAppInstancesApi(sandbox_api)\n", + " result = nw.sandbox_app_instances_get(sandbox_name, async_req = False) # noqa: E501\n", + " logger.debug('get_applications_list: result: ' + str(result))\n", + " return result\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxAppInstancesApi->get_applications_list: %s\\n' % e)\n", + " return None \n", + " # End of function delete_application_instance_id" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Putting everything together\n", + "\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Login\n", + "- Print sandbox identifier\n", + "- Print available network scenarios\n", + "- Activate a network scenario\n", + "- Request for a new application instance identifier\n", + "- Retrieve the list of the applications instance identifier\n", + "- Check the demo application is present in the list of applications\n", + "- Delete our application instance identifier\n", + "- Deactivate a network scenario\n", + "- Logout\n", + "- Check that logout is effective\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Request for a new application instance identifier\n", + " - Retrieve the list of the applications instance identifier\n", + " - Check the demo application is present in the list of applications\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " - Check that logout is effective\n", + " \"\"\" \n", + " global logger, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " logger.error('Failed to instanciate a MEC Sandbox')\n", + " return\n", + "\n", + " # Print sandbox identifier\n", + " logger.info('Sandbox created: ' + sandbox)\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Print available network scenarios\n", + " nw_scenarios = get_network_scenarios(sandbox)\n", + " if nw_scenarios is None:\n", + " logger.error('Failed to retrieve the list of network scenarios')\n", + " elif len(nw_scenarios) != 0:\n", + " logger.info('nw_scenarios: %s', str(type(nw_scenarios[0])))\n", + " logger.info('nw_scenarios: %s', str(nw_scenarios))\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + " else:\n", + " logger.info('nw_scenarios: No scenario available')\n", + "\n", + " # Activate a network scenario based on a list of criterias (hard coded!!!)\n", + " if activate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to activate network scenario')\n", + " else:\n", + " logger.info('Network scenario activated: ' + nw_scenarios[nw_scenario_idx].id)\n", + " # Wait for the MEC services are running\n", + " time.sleep(2 * STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Request for a new application instance identifier\n", + " app_inst_id = request_application_instance_id(sandbox)\n", + " if app_inst_id == None:\n", + " logger.error('Failed to request an application instance identifier')\n", + " else:\n", + " logger.info('app_inst_id: %s', str(type(app_inst_id)))\n", + " logger.info('app_inst_id: %s', str(app_inst_id))\n", + "\n", + " # Check the demo application is present in the list of applications\n", + " app_list = get_applications_list(sandbox)\n", + " if app_list is None:\n", + " logger.error('Failed to request the list of applications')\n", + " else:\n", + " logger.info('app_list: %s', str(type(app_list)))\n", + " logger.info('app_list: %s', str(app_list))\n", + " # Check if our application is present in the list of applications\n", + " found = False\n", + " for item in app_list:\n", + " if item.id == app_inst_id.id:\n", + " found = True\n", + " break\n", + " if not found:\n", + " logger.error('Failed to retrieve our application instance identifier')\n", + "\n", + " # Delete the application instance identifier\n", + " if delete_application_instance_id(sandbox, app_inst_id.id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + " else:\n", + " logger.info('app_inst_id deleted: ' + app_inst_id.id)\n", + "\n", + " # Deactivate a network scenario based on a list of criterias (hard coded!!!)\n", + " if deactivate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to deactivate network scenario')\n", + " else:\n", + " logger.info('Network scenario deactivated: ' + nw_scenarios[nw_scenario_idx].id)\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(2 * STABLE_TIME_OUT)\n", + "\n", + " # Logout\n", + " process_logout(sandbox)\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective, verify on the MEC Sandbox server that the MEC Sandbox is removed (kubectl get pods -A)')\n", + " \n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MEC Registration and the READY confirmation\n", + "\n", + "Having an application instance identifier allows us to register with the MEC Sandbox and interact with it (e.g. to send service queries, to subscribe to events and to recieve notifications...).\n", + "\n", + "The standard MEC 011 Clause 5.2.2 MEC application start-up describes the start up process. Basically, our MEC application has to:\n", + "1. Indicates that it is running by sending a Confirm Ready message\n", + "2. Retrieve the list of MEC services \n", + "\n", + "To do so, a MEC application needs to be able to send requests but also to receive notifications (POST requests) and to reply to them." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Fifth step: Send the READY confirmation\n", + "\n", + "The MEC application instance confirms towards the MEC platform that it is up and running. It corresponds to step 4c described inETSI GS MEC 011 V3.2.1 (2024-04)11 Clause 5.2.2 MEC application start-up.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def send_ready_confirmation(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> int:\n", + " \"\"\"\n", + " Send the ready_confirmation to indicate that the MEC application is active.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: 0 on success, -1 otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.2 MEC application start-up - Step 4c\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_ready_confirmation: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/confirm_ready'\n", + " logger.debug('send_ready_confirmation: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # JSON indication READY\n", + " dict_body = {}\n", + " dict_body['indication'] = 'READY'\n", + " service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=dict_body, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function send_ready_confirmation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition, our MEC application is registering to AppTerminationNotificationSubscription and it needs to delete its subscription when terminating.\n", + "\n", + "At this stage, it is important to note that all subscription deletion use the same format: / (see ETSI MEC GS 003 [16]). \n", + "In this case, it the AppTerminationNotificationSubscription is 'sub-1234', the URIs to do the susbscription and to delete it are:\n", + "- MEC_SANDBOX_URL + '/' + sandbox_name + '/' + MEC_PLTF + '/mec_app_support/v2/applications/' + app_inst_id + '/subscriptions'\n", + "- MEC_SANDBOX_URL + '/' + sandbox_name + '/' + MEC_PLTF + '/mec_app_support/v2/applications/' + app_inst_id + '/subscriptions/sub-1234'\n", + "\n", + "So, it will be usefull to create a small function to extract the subscription identifier from either the HTTP Location header or from the Link field found into the reponse body data structure. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Subscribing to application termination\n", + "\n", + "The purpose is to create a new subscription to \n", + "the MEC application termination notification as describe in ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.6b Receiving event notifications on MEC application instance \n", + "terminations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def send_subscribe_termination(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> object:\n", + " \"\"\"\n", + " Subscribe to the MEC application termination notifications.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: The HTTP respone, the subscription ID and the resource URL on success, None otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.6b Receiving event notifications on MEC application instance termination\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_subscribe_termination: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/subscriptions'\n", + " logger.debug('send_subscribe_termination: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # Body\n", + " dict_body = {}\n", + " dict_body['subscriptionType'] = 'AppTerminationNotificationSubscription'\n", + " dict_body['callbackReference'] = CALLBACK_URI + '/mec011/v2/termination' # FIXME To be parameterized\n", + " dict_body['appInstanceId'] = app_inst_id.id\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=dict_body, async_req=False)\n", + " return (result, extract_sub_id(headers['Location']), headers['Location'])\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status, None)\n", + " # End of function send_subscribe_termination" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Extracting subscription identifier\n", + "\n", + "This helper function extracts the subscription identifier from any subscription URL." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def extract_sub_id(resource_url: str) -> str:\n", + " \"\"\"\n", + " Extract the subscription identifier from the specified subscription URL.\n", + " :param resource_url: The subscription URL\n", + " :return: The subscription identifier on success, None otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> extract_sub_id: resource_url: ' + resource_url)\n", + "\n", + " res = urllib3.util.parse_url(resource_url)\n", + " if res is not None and res.path is not None and res.path != '':\n", + " id = res.path.rsplit('/', 1)[-1]\n", + " if id is not None:\n", + " return id\n", + " return None\n", + " # End of function extract_sub_id" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Delete subscription to application termination" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def delete_subscribe_termination(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo, sub_id: str) -> int:\n", + " \"\"\"\n", + " Delete the subscrition to the AppTermination notification.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> delete_subscribe_termination: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/subscriptions/{sub_id}'\n", + " logger.debug('delete_subscribe_termination: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " path_params['sub_id'] = sub_id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " service_api.call_api(url, 'DELETE', header_params=header_params, path_params = path_params, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function delete_subscribe_termination" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When the MEC application instance is notified to gracefully terminate, it provides to the MEC platform \n", + "that the application has completed its application level related terminate/stop actiono\n", + "\n", + "Reference: ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.3 MEC application graceful termination/stopp." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def send_termination_confirmation(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> int:\n", + " \"\"\"\n", + " Send the confirm_termination to indicate that the MEC application is terminating gracefully.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: 0 on success, -1 otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.3 MEC application graceful termination/stop\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_termination_confirmation: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/confirm_termination'\n", + " logger.debug('send_termination_confirmation: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # JSON indication READY\n", + " dict_body = {}\n", + " dict_body['operationAction'] = 'TERMINATING'\n", + " service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=dict_body, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function send_termination_confirmation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Putting everythig together\n", + "Now, it is time now to create the our fifth iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Login\n", + "- Print sandbox identifier\n", + "- Print available network scenarios\n", + "- Activate a network scenario\n", + "- Request for a new application instance identifier\n", + "- Send READY confirmation\n", + "- Subscribe to AppTerminationNotificationSubscription\n", + "- Check list of services\n", + "- Delete AppTerminationNotification subscription\n", + "- Delete our application instance identifier\n", + "- Deactivate a network scenario\n", + "- Logout\n", + "- Check that logout is effective\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Request for a new application instance identifier\n", + " - Send READY confirmation\n", + " \n", + " - Subscribe to AppTermination Notification\n", + " - Send Termination\n", + " - Delete AppTerminationNotification subscription\n", + " - Delete our application instance identifier\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " - Check that logout is effective\n", + " \"\"\" \n", + " global logger, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " logger.error('Failed to instanciate a MEC Sandbox')\n", + " return\n", + "\n", + " # Print sandbox identifier\n", + " logger.info('Sandbox created: ' + sandbox)\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Print available network scenarios\n", + " nw_scenarios = get_network_scenarios(sandbox)\n", + " if nw_scenarios is None:\n", + " logger.error('Failed to retrieve the list of network scenarios')\n", + " elif len(nw_scenarios) != 0:\n", + " logger.info('nw_scenarios: %s', str(type(nw_scenarios[0])))\n", + " logger.info('nw_scenarios: %s', str(nw_scenarios))\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + " else:\n", + " logger.info('nw_scenarios: No scenario available')\n", + "\n", + " # Activate a network scenario based on a list of criterias (hard coded!!!)\n", + " if activate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to activate network scenario')\n", + " else:\n", + " logger.info('Network scenario activated: ' + nw_scenarios[nw_scenario_idx].id)\n", + " # Wait for the MEC services are running\n", + " time.sleep(2 * STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Request for a new application instance identifier\n", + " app_inst_id = request_application_instance_id(sandbox)\n", + " if app_inst_id == None:\n", + " logger.error('Failed to request an application instance identifier')\n", + " else:\n", + " logger.info('app_inst_id: %s', str(app_inst_id))\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Send READY confirmation\n", + " sub_id = None\n", + " if send_ready_confirmation(sandbox, app_inst_id) == -1:\n", + " logger.error('Failed to send confirm_ready')\n", + " else:\n", + " # Subscribe to AppTerminationNotificationSubscription\n", + " result, sub_id, res_url = send_subscribe_termination(sandbox, app_inst_id)\n", + " if sub_id == None:\n", + " logger.error('Failed to do the subscription')\n", + " else:\n", + " logger.info('result: ' + str(result))\n", + " logger.info('sub_id: %s', sub_id)\n", + " data = json.loads(result.data)\n", + " logger.info('data: ' + str(data))\n", + "\n", + " # Any processing here\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Delete AppTerminationNotification subscription\n", + " if sub_id is not None:\n", + " if delete_subscribe_termination(sandbox, app_inst_id, sub_id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + " else:\n", + " logger.info('app_inst_id deleted: ' + app_inst_id.id)\n", + "\n", + " # Delete the application instance identifier\n", + " if delete_application_instance_id(sandbox, app_inst_id.id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + " else:\n", + " logger.info('app_inst_id deleted: ' + app_inst_id.id)\n", + "\n", + " # Deactivate a network scenario based on a list of criterias (hard coded!!!)\n", + " if deactivate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to deactivate network scenario')\n", + " else:\n", + " logger.info('Network scenario deactivated: ' + nw_scenarios[nw_scenario_idx].id)\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(2 * STABLE_TIME_OUT)\n", + "\n", + " # Logout\n", + " process_logout(sandbox)\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective, verify on the MEC Sandbox server that the MEC Sandbox is removed (kubectl get pods -A)')\n", + " \n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Conclusion: Create two procedures for the setup and the termination of our MEC application\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The procedure for the setup of a MEC application\n", + "\n", + "This function provides the steps to setup a MEC application and to be ready to use the MEC service exposed by the created MEC Sandbox.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def mec_app_setup():\n", + " \"\"\"\n", + " This function provides the steps to setup a MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Request for a new application instance identifier\n", + " - Send READY confirmation\n", + " - Subscribe to AppTermination Notification\n", + " :return The MEC Sandbox instance, the MEC application instance identifier and the subscription identifier on success, None otherwise\n", + " \"\"\"\n", + " global logger, nw_scenarios\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " logger.error('Failed to instanciate a MEC Sandbox')\n", + " return None\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Print available network scenarios\n", + " nw_scenarios = get_network_scenarios(sandbox)\n", + " if nw_scenarios is None:\n", + " logger.error('Failed to retrieve the list of network scenarios')\n", + " elif len(nw_scenarios) != 0:\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + " else:\n", + " logger.info('nw_scenarios: No scenario available')\n", + "\n", + " # Activate a network scenario based on a list of criterias (hard coded!!!)\n", + " if activate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to activate network scenario')\n", + " else:\n", + " # Wait for the MEC services are running\n", + " time.sleep(2 * STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Request for a new application instance identifier\n", + " app_inst_id = request_application_instance_id(sandbox)\n", + " if app_inst_id == None:\n", + " logger.error('Failed to request an application instance identifier')\n", + " else:\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Send READY confirmation\n", + " sub_id = None\n", + " if send_ready_confirmation(sandbox, app_inst_id) == -1:\n", + " logger.error('Failed to send confirm_ready')\n", + " else:\n", + " # Subscribe to AppTerminationNotificationSubscription\n", + " result, sub_id, res_url = send_subscribe_termination(sandbox, app_inst_id)\n", + " if sub_id == None:\n", + " logger.error('Failed to do the subscription')\n", + "\n", + " return (sandbox, app_inst_id, sub_id)\n", + " # End of function mec_app_setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The procedure for the termination of a MEC application\n", + "\n", + "This function provides the steps to terminate a MEC application.\n", + "\n", + "**Note:** All subscriptions done outside of the mec_app_setup function are not deleted." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def mec_app_termination(sandbox_name: str, app_inst_id:swagger_client.models.ApplicationInfo, sub_id: str):\n", + " \"\"\"\n", + " This function provides the steps to setup a MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Request for a new application instance identifier\n", + " - Send READY confirmation\n", + " - Subscribe to AppTermination Notification\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " \"\"\"\n", + " global logger\n", + "\n", + " # Delete AppTerminationNotification subscription\n", + " if sub_id is not None:\n", + " if delete_subscribe_termination(sandbox_name, app_inst_id, sub_id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + "\n", + " # Delete the application instance identifier\n", + " if delete_application_instance_id(sandbox_name, app_inst_id.id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + " else:\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Deactivate a network scenario based on a list of criterias (hard coded!!!)\n", + " if deactivate_network_scenario(sandbox_name) == -1:\n", + " logger.error('Failed to deactivate network scenario')\n", + " else:\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(2 * STABLE_TIME_OUT)\n", + "\n", + " # Logout\n", + " process_logout(sandbox_name)\n", + " # End of function mec_app_termination" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell describes the new basic MEC application architecture. It will be used in the rest of this titorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Mec application setup\n", + " - Get UU unicast provisioning information\n", + " - Mec application termination\n", + " \"\"\" \n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Any processing here\n", + " logger.info('sandbox_name: ' + sandbox_name)\n", + " logger.info('app_inst_id: ' + app_inst_id.id)\n", + " if sub_id is not None:\n", + " logger.info('sub_id: ' + sub_id)\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create our second MEC application: how to use MEC Location Service\n", + "\n", + "After doing the logging, network scenario activation, MEC application instance creation steps, we are ready to exploit the MEC services exposed by the MEC Sandbox.\n", + "\n", + "In this clause, we use the following functionalities provided by MEC-013 LocationAPIs:\n", + "- Getting UE location lookup (ETSI GS MEC 013 Clause 5.3.2 UE Location Lookup)\n", + "- Subscribe to the UE Location changes (ETSI GS MEC 030 Clause 5.3.4)\n", + "- Delete subscription\n", + "\n", + "### First step: Getting UE location lookup\n", + "\n", + "First of all, just create a function to request the location of a specific UE. The UE identifier depends of the network scenario which was activated. In our example, we are using the 4g-5g-macro-v2x network scenario and we will run our code with the high velocity UE (i.e., the cars such as 10.100.0.1).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_ue_location(sandbox_name: str, ue: str) -> object:\n", + " \"\"\"\n", + " To retrieves the location information of an UE identified by its IPv4 address (e.g. 10.100.0.1)\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return The HTTP response and the response status on success, None otherwise\n", + " :see ETSI GS MEC 013 V3.1.1 (2023-01) Clause 5.3.2 UE Location Lookup\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> get_ue_location: ' + ue)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/location/v3/queries/users'\n", + " logger.debug('get_ue_location: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " query_params = []\n", + " query_params.append(('address', ue))\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, query_params=query_params, async_req=False)\n", + " return (result, status)\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status)\n", + " # End of function get_ue_location" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create our new MEC application. The expected result looks like:\n", + "```json\n", + "{\n", + " \"userList\": {\n", + " \"resourceURL\": \"https://mec-platform2.etsi.org/xxx/mep1/location/v3/queries/users\",\n", + " \"user\": [\n", + " {\n", + " \"address\": \"10.100.0.1\",\n", + " \"accessPointId\": \"4g-macro-cell-6\",\n", + " \"zoneId\": \"zone03\",\n", + " \"resourceURL\": \"https://mec-platform.etsi.org/xxx/mep1/location/v3/queries/users?address=10.100.0.1\",\n", + " \"timeStamp\": {\n", + " \"nanoSeconds\": 0,\n", + " \"seconds\": 1729245754\n", + " },\n", + " \"locationInfo\": {\n", + " \"latitude\": [\n", + " 43.73707\n", + " ],\n", + " \"longitude\": [\n", + " 7.422555\n", + " ],\n", + " \"shape\": 2\n", + " },\n", + " \"civicInfo\": {\n", + " \"country\": \"MC\"\n", + " },\n", + " \"relativeLocationInfo\": {\n", + " \"X\": 630.37036,\n", + " \"Y\": 261.92648,\n", + " \"mapInfo\": {\n", + " \"mapId\": \"324561243\",\n", + " \"origin\": {\n", + " \"latitude\": 43.7314,\n", + " \"longitude\": 7.4202\n", + " }\n", + " }\n", + " }\n", + " }\n", + " ]\n", + " }\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together\n", + "\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Login\n", + "- Activate a network scenario\n", + "- Getting UE location lookup (ETSI GS MEC 013 Clause 5.3.2)\n", + "- Delete our application instance identifier\n", + "- Deactivate a network scenario\n", + "- Logout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Activate a network scenario\n", + " - Getting UE location lookup (ETSI GS MEC 013 Clause 5.3.2)\n", + " - Delete our application instance identifier\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " \"\"\" \n", + " global logger, nw_scenarios\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Getting UE location lookup\n", + " result, status = get_ue_location(sandbox_name, '10.100.0.1')\n", + " logger.info('UE location information: status: %s', str(status))\n", + " if status != 200:\n", + " logger.error('Failed to get UE location information')\n", + " else:\n", + " logger.info('UE location information: %s', str(result.data))\n", + "\n", + " # Any processing comes here\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('UserList: ' + str(data))\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Second step: Adding a subscription\n", + "\n", + "The purpose here is to create a subscritpion when the UE (e.g. 10.100.0.1) is entering or leaving a PoA area (PoA is tanding for Piont of Access).\n", + "According to ETSI GS MEC 013 V3.1.1 Clause 7.5.3.4 POST, the 'request method is a POST and the endpoint is '/location/v3/subscriptions/users/'.\n", + "\n", + "The cell below provides the code to create our subscription." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Notification support\n", + "\n", + "To recieve notification, our MEC application is required to support an HTTP listenener to recieve POST requests from the MEC Sandbox and reply to them: this is the notification mechanism.\n", + "\n", + "This minimalistic HTTP server will also be used to implement the endpoints provided by our MEC application service: see chapter [Our third MEC application: how to create a new MEC Services](#our_third_mec_application_how_to_create_a_new_mec_services).\n", + "\n", + "The class HTTPRequestHandler (see cell below) provides the suport of such mechanism.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class HTTPServer_RequestHandler(BaseHTTPRequestHandler):\n", + " \"\"\"\n", + " Minimal implementation of an HTTP server (http only).\n", + " \"\"\"\n", + "\n", + " def do_GET(self):\n", + " logger.info('>>> do_GET: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_GET: ' + ctype)\n", + "\n", + " message = ''\n", + " if self.path == '/sandbox/v1/statistic/v1/quantity':\n", + " logger.info('do_GET: Computing statistic quantities for application MEC service')\n", + " # TODO Add logit to our MEC service\n", + " message = '{\"time\":20180124,\"avg\": 0.0,\"max\": 0.0,\"min\": 0.0,\"stddev\": 0.0 }'\n", + " else:\n", + " # Send error message\n", + " message = '{\"title\":\"Unknown URI\",\"type\":\"do_GET.parser\",\"status\":404 }'\n", + " logger.info('do_GET: message: ' + message)\n", + " \n", + " # Send response status code\n", + " self.send_response(HTTPStatus.OK)\n", + "\n", + " # Send headers\n", + " self.send_header('Content-type','text/plain; charset=utf-8')\n", + " self.send_header('Content-length', str(len(message)))\n", + " self.end_headers()\n", + "\n", + " # Write content as utf-8 data\n", + " self.wfile.write(message.encode('utf8'))\n", + " return\n", + " # End of function do_GET\n", + "\n", + " def do_POST(self):\n", + " global got_notification\n", + "\n", + " logger.info('>>> do_POST: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_POST: ' + ctype)\n", + "\n", + " content_len = int(self.headers.get('Content-Length'))\n", + " if content_len != 0:\n", + " body = self.rfile.read(content_len).decode('utf8')\n", + " logger.info('do_POST: body:' + str(type(body)))\n", + " logger.info('do_POST: body:' + str(body))\n", + " data = json.loads(str(body))\n", + " logger.info('do_POST: data: %s', str(data))\n", + "\n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " got_notification = True\n", + " return\n", + " # End of function do_POST\n", + "\n", + " def do_PUT(self):\n", + " logger.info('>>> do_PUT: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_PUT: ' + ctype)\n", + "\n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " return\n", + " # End of function do_PUT\n", + "\n", + " def do_PATCH(self):\n", + " logger.info('>>> do_PATCH: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_PATCH: ' + ctype)\n", + " \n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " return\n", + " # End of function do_PATCH\n", + " # End of class HTTPRequestHandler\n", + "\n", + " def do_DELETE(self):\n", + " logger.info('>>> do_DELETE: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_DELETE: ' + ctype)\n", + " \n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " return\n", + " # End of function do_DELETE\n", + " # End of class HTTPRequestHandler\n", + "\n", + "def start_notification_server() -> HTTPServer:\n", + " \"\"\"\n", + " Start the notification server\n", + " :return The instance of the HTTP server\n", + " \"\"\"\n", + " global LISTENER_PORT, got_notification, logger\n", + "\n", + " logger.debug('>>> start_notification_server on port ' + str(LISTENER_PORT))\n", + " got_notification = False\n", + " server_address = ('', LISTENER_PORT)\n", + " httpd = HTTPServer(server_address, HTTPServer_RequestHandler)\n", + " # Start notification server in a daemonized thread\n", + " notification_server = threading.Thread(target = httpd.serve_forever, name='notification_server')\n", + " notification_server.daemon = True\n", + " notification_server.start()\n", + " return httpd\n", + " # End of function HTTPRequestHandler\n", + "\n", + "def stop_notification_server(httpd: HTTPServer):\n", + " \"\"\"\n", + " Stop the notification server\n", + " :param The instance of the HTTP server\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> stop_notification_server')\n", + " httpd.server_close()\n", + " httpd=None\n", + " # End of function HTTPRequestHandler\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def subscribe_for_user_events(sandbox_name: str) -> object:\n", + " \"\"\"\n", + " Subscriptions for notifications related to location.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: The HTTP respone, the subscription ID and the resource URL on success, None otherwise\n", + " :see ETSI GS MEC 013 V3.1.1 Clause 7.5 Resource: user_subscriptions\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> subscribe_for_user_events: ' + sandbox_name)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}//location/v3/subscriptions/users'\n", + " logger.debug('subscribe_for_user_events: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # Body\n", + " dict_body = {}\n", + " dict_body['subscriptionType'] = 'UserLocationEventSubscription'\n", + " dict_body['callbackReference'] = CALLBACK_URI + '/mec013/v3/location' # FIXME To be parameterized\n", + " dict_body['address'] = '10.100.0.1' # FIXME To be parameterized\n", + " dict_body['clientCorrelator'] = \"12345\"\n", + " dict_body['locationEventCriteria'] = [ \"ENTERING_AREA_EVENT\", \"LEAVING_AREA_EVENT\"]\n", + " m = {}\n", + " m[\"userLocationEventSubscription\"] = dict_body\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=m, async_req=False)\n", + " return (result, extract_sub_id(headers['Location']), headers['Location'])\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, None, None)\n", + " # End of function subscribe_for_user_are_event" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together\n", + "\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Login\n", + "- Activate a network scenario\n", + "- Create subscription\n", + "- Wait for notification\n", + "- Delete our application instance identifier\n", + "- Deactivate a network scenario\n", + "- Logout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Activate a network scenario\n", + " - Create subscription\n", + " - Wait for notification\n", + " - Delete our application instance identifier\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " \"\"\" \n", + " global logger, nw_scenarios, got_notification\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Start notification server in a daemonized thread\n", + " httpd = start_notification_server()\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Create subscription\n", + " result, status, headers = subscribe_for_user_events(sandbox_name)\n", + " logger.info('UE location information: status: %s', str(status))\n", + " if status != 200:\n", + " logger.error('Failed to get UE location information')\n", + " else:\n", + " logger.info('UE location information: ' + str(result.data))\n", + " \n", + " # Getting UE location lookup\n", + " result, status = get_ue_location(sandbox_name, '10.100.0.1')\n", + " logger.info('UE location information: status: ' + str(status))\n", + " if status != 200:\n", + " logger.error('Failed to get UE location information')\n", + " else:\n", + " logger.info('UE location information: ' + str(result.data))\n", + "\n", + " # Wait for the notification\n", + " counter = 0\n", + " while not got_notification and counter < 30:\n", + " logger.info('Waiting for subscription...')\n", + " time.sleep(STABLE_TIME_OUT)\n", + " counter += 1\n", + " # End of 'while' statement\n", + "\n", + " # Stop notification server\n", + " stop_notification_server(httpd)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create our third MEC application: how to use V2X MEC Services\n", + "\n", + "After doing the logging, network scenario activation, MEC application instance creation steps, we are ready to exploit the MEC services exposed by the MEC Sandbox.\n", + "\n", + "In this clause, we use the following functionalities provided by MEC-030:\n", + "- Getting UU unicast provisioning information (ETSI GS MEC 030 Clause 5.5.1)\n", + "- Subscribe to the V2X message distribution server (ETSI GS MEC 030 Clause 5.5.7)\n", + "- Delete subscription\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Getting UU unicast provisioning information\n", + "\n", + "The purpose is to query provisioning information for V2X communication over Uu unicast." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def send_uu_unicast_provisioning_info(sandbox_name: str, ecgi: str) -> object:\n", + " \"\"\"\n", + " Request for V2X communication over Uu unicast information\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param ecgi: Comma separated list of locations to identify a cell of a base station or a particular geographical area\n", + " :return The Uu unicast provisioning information on success, None otherwise\n", + " :see ETSI GS MEC 030 V3.2.1 (2024-02) Clause 5.5.1 Sending a request for provisioning information for V2X communication over Uu unicast\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_uu_unicast_provisioning_info: ' + ecgi)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/vis/v2/queries/uu_unicast_provisioning_info'\n", + " logger.debug('send_uu_unicast_provisioning_info: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " query_params = []\n", + " query_params.append(('location_info', 'ecgi,' + ecgi))\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, header) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, query_params=query_params, async_req=False)\n", + " return (result, status, header)\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status, None)\n", + " # End of function send_uu_unicast_provisioning_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create the our second MEC application.\n", + "The sequence is the following:\n", + "- Mec application setup\n", + "- Get UU unicast provisioning information\n", + "- Mec application termination\n", + "\n", + "Note that the UU unicast provisioning information is returned as a JSON string. To de-serialized it into a Python data structure, please refer to clause [Subscribing to V2X message distribution server](#subscribing_to_v2x_message_distribution_server)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Mec application setup\n", + " - Get UU unicast provisioning information\n", + " - Mec application termination\n", + " \"\"\" \n", + " global logger, nw_scenarios\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Get UU unicast provisioning information\n", + " ecgi = \"268708941961,268711972264\" # List of ecgi spearated by a ','\n", + " result, status, header = send_uu_unicast_provisioning_info(sandbox_name, ecgi)\n", + " logger.info('UU unicast provisioning information: status: %s', str(status))\n", + " if status != 200:\n", + " logger.error('Failed to get UU unicast provisioning information')\n", + " else:\n", + " logger.info('UU unicast provisioning information: %s', str(result.data))\n", + "\n", + " # Any processing comes here\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: ' + str(data))\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subscribing to V2X message distribution server\n", + "\n", + "Here, we need to come back to the MEC 030 standard to create the type V2xMsgSubscription. It involves the creation of a set of basic types described below.\n", + "\n", + "These new type shall be 'JSON' serializable. It means that they have to implement the following methods:\n", + "\n", + "- to_dict()\n", + "- to_str()\n", + "- \\_\\_repr\\_\\_()\n", + "- \\_\\_eq\\_\\_()\n", + "- \\_\\_ne\\_\\_()\n", + "\n", + "**Reference:** ETSI GS MEC 030 V3.2.1 (2024-02) Clause 6.5.13 Type: LinkType\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class LinkType(object):\n", + " swagger_types = {'href': 'str'}\n", + " attribute_map = {'href': 'href'}\n", + " def __init__(self, href=None): # noqa: E501\n", + " self._href = None\n", + " if href is not None:\n", + " self._href = href\n", + " @property\n", + " def href(self):\n", + " return self._href\n", + " @href.setter\n", + " def href(self, href):\n", + " self._href = href\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(LinkType, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, LinkType):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class Links(object):\n", + " swagger_types = {'self': 'LinkType'}\n", + " attribute_map = {'self': 'self'}\n", + " def __init__(self, self_=None): # noqa: E501\n", + " self._self = None\n", + " if self_ is not None:\n", + " self._self = self_\n", + " @property\n", + " def self_(self):\n", + " return self._self\n", + " @self_.setter\n", + " def self_(self, self_):\n", + " self._self = self_\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(Links, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, Links):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class timeStamp(object):\n", + " swagger_types = {'seconds': 'int', 'nano_seconds': 'int'}\n", + " attribute_map = {'seconds': 'seconds', 'nano_seconds': 'nanoSeconds'}\n", + " def __init__(self, seconds=None, nano_seconds=None): # noqa: E501\n", + " self._seconds = None\n", + " self._nano_seconds = None\n", + " if seconds is not None:\n", + " self._seconds = seconds\n", + " if nano_seconds is not None:\n", + " self._nano_seconds = nano_seconds\n", + " @property\n", + " def seconds(self):\n", + " return self._seconds\n", + " @seconds.setter\n", + " def seconds(self, seconds):\n", + " self._seconds = seconds\n", + " @property\n", + " def nano_seconds(self):\n", + " return self._nano_seconds\n", + " @nano_seconds.setter\n", + " def nano_seconds(self, nano_seconds):\n", + " self._nano_seconds = nano_seconds\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(timeStamp, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, timeStamp):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell below implements the V2xMsgSubscription data structure.\"}\n", + "Reference: ETSI GS MEC 030 V3.2.1 (2024-02) Clause 6.3.5 Type: V2xMsgSubscription\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class V2xMsgSubscription(object):\n", + " swagger_types = {'links': 'Links', 'callback_reference': 'str', 'filter_criteria': 'V2xMsgSubscriptionFilterCriteria', 'request_test_notification': 'bool', 'subscription_type': 'str'}\n", + " attribute_map = {'links': 'Links', 'callback_reference': 'callbackReference', 'filter_criteria': 'filterCriteria', 'request_test_notification': 'requestTestNotification', 'subscription_type': 'subscriptionType'}\n", + " def __init__(self, links=None, callback_reference=None, filter_criteria=None, request_test_notification=None): # noqa: E501\n", + " self._links = None\n", + " self._callback_reference = None\n", + " self._filter_criteria = None\n", + " self._request_test_notification = None\n", + " self._subscription_type = \"V2xMsgSubscription\"\n", + " if links is not None:\n", + " self.links = links\n", + " if callback_reference is not None:\n", + " self.callback_reference = callback_reference\n", + " if filter_criteria is not None:\n", + " self.filter_criteria = filter_criteria\n", + " if request_test_notification is not None:\n", + " self.request_test_notification = request_test_notification\n", + " @property\n", + " def links(self):\n", + " return self._links\n", + " @links.setter\n", + " def links(self, links):\n", + " self_.links = links\n", + " @property\n", + " def callback_reference(self):\n", + " return self._callback_reference\n", + " @callback_reference.setter\n", + " def callback_reference(self, callback_reference):\n", + " self._callback_reference = callback_reference\n", + " @property\n", + " def links(self):\n", + " return self._links\n", + " @links.setter\n", + " def links(self, links):\n", + " self._links = links\n", + " @property\n", + " def filter_criteria(self):\n", + " return self._filter_criteria\n", + " @filter_criteria.setter\n", + " def filter_criteria(self, filter_criteria):\n", + " self._filter_criteria = filter_criteria\n", + " @property\n", + " def request_test_notification(self):\n", + " return self._request_test_notification\n", + " @request_test_notification.setter\n", + " def request_test_notification(self, request_test_notification):\n", + " self._request_test_notification = request_test_notification\n", + " @property\n", + " def subscription_type(self):\n", + " return self._subscription_type\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(V2xMsgSubscription, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, V2xMsgSubscription):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class V2xMsgSubscriptionFilterCriteria(object):\n", + " swagger_types = {'msg_type': 'list[str]', 'std_organization': 'str'}\n", + " attribute_map = {'msg_type': 'MsgType', 'std_organization': 'stdOrganization'}\n", + " def __init__(self, msg_type, std_organization): # noqa: E501\n", + " self._msg_type = None\n", + " self._std_organization = None\n", + " self.msg_type = msg_type\n", + " self.std_organization = std_organization\n", + " @property\n", + " def msg_type(self):\n", + " return self._msg_type\n", + " @msg_type.setter\n", + " def msg_type(self, msg_type):\n", + " self._msg_type = msg_type\n", + " @property\n", + " def std_organization(self):\n", + " return self._std_organization\n", + " @std_organization.setter\n", + " def std_organization(self, std_organization):\n", + " self._std_organization = std_organization\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(V2xMsgSubscriptionFilterCriteria, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, V2xMsgSubscriptionFilterCriteria):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is the V2X message subscription function. The HTTP Request message body contains a 'JSON' serialized instance of the class V2xMsgSubscription.\n", + "\n", + "Reference: ETSI GS MEC 030 V3.2.1 (2024-02) Clause 5.5.10 V2X message interoperability\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def subscribe_v2x_message(sandbox_name: str, v2xMsgSubscription: V2xMsgSubscription) -> object:\n", + " \"\"\"\n", + " Request to subscribe the V2X messages which come from different vehicle OEMs or operators\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " :return The HTTP response, the HTTP response status, the subscription identifier and the subscription URL on success, None otherwise\n", + " :see ETSI GS MEC 030 V3.2.1 (2024-02) Clause 5.5.10 V2X message interoperability\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> subscribe_v2x_message: v2xMsgSubscription: ' + str(v2xMsgSubscription))\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/vis/v2/subscriptions'\n", + " logger.debug('subscribe_v2x_message: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params=path_params, body=v2xMsgSubscription, async_req=False)\n", + " return (result, status, extract_sub_id(headers['Location']), headers['Location'])\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status, None)\n", + " # End of function subscribe_v2x_message" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is a generic function to delete any MEC service subscription based on the subscription resource URL provided in the Location header of the subscription creation response." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def delete_mec_subscription(resource_url: str) -> int:\n", + " \"\"\"\n", + " Delete any existing MEC subscription\n", + " :param resource_url: The subscription URL\n", + " :return 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> delete_mec_subscription: resource_url: ' + resource_url)\n", + " try:\n", + " res = urllib3.util.parse_url(resource_url)\n", + " if res is None:\n", + " logger.error('delete_mec_subscription: Failed to paerse URL')\n", + " return -1\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " service_api.call_api(res.path, 'DELETE', header_params=header_params, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finaly, here is how to implement the V2X message subscription:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Mec application setup\n", + " - Subscribe to V2XMessage\n", + " - Delete subscription\n", + " - Mec application termination\n", + " \"\"\" \n", + " global MEC_PLTF, CALLBACK_URI, logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Create a V2X message subscritpion\n", + " filter_criteria = V2xMsgSubscriptionFilterCriteria(['1', '2'], 'ETSI')\n", + " v2xMsgSubscription = V2xMsgSubscription(callback_reference = CALLBACK_URI + '/vis/v2/v2x_msg_notification', filter_criteria = filter_criteria)\n", + " result, status, v2x_sub_id, v2x_resource = subscribe_v2x_message(sandbox_name, v2xMsgSubscription)\n", + " if status != 201:\n", + " logger.error('Failed to create subscription')\n", + "\n", + " # Any processing here\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: %s', str(data))\n", + " logger.info('app_inst_id: ' + app_inst_id.id)\n", + " if sub_id is not None:\n", + " logger.info('sub_id: ' + sub_id)\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Delete the V2X message subscritpion\n", + " delete_mec_subscription(v2x_resource)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together\n", + "\n", + "let's add a subscription the our previous MEC application.\n", + "The sequence is the following:\n", + "- Mec application setup\n", + "- Start the notification server\n", + "- Get UU unicast provisioning information\n", + "- Add subscription\n", + "- Stop the notification server\n", + "- Mec application termination" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the third sprint of our skeleton of our MEC application:\n", + " - Mec application setup\n", + " - Start the notification server\n", + " - Get UU unicast provisioning information\n", + " - Add subscription\n", + " - Stop the notification server\n", + " - Mec application termination\n", + " \"\"\" \n", + " global CALLBACK_URI, logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Get UU unicast provisioning information\n", + " ecgi = \"268708941961,268711972264\" # List of ecgi spearated by a ','\n", + " result = send_uu_unicast_provisioning_info(sandbox_name, ecgi)\n", + " if result is None:\n", + " logger.error('Failed to get UU unicast provisioning information')\n", + " else:\n", + " logger.info('UU unicast provisioning information: ' + str(result))\n", + "\n", + " # Start notification server in a daemonized thread\n", + " httpd = start_notification_server()\n", + "\n", + " # Create a V2X message subscritpion\n", + " filter_criteria = V2xMsgSubscriptionFilterCriteria(['1', '2'], 'ETSI')\n", + " v2xMsgSubscription = V2xMsgSubscription(callback_reference = CALLBACK_URI + '/vis/v2/v2x_msg_notification', filter_criteria = filter_criteria)\n", + " result, status, v2x_sub_id, v2x_resource = subscribe_v2x_message(sandbox_name, v2xMsgSubscription)\n", + " if status != 201:\n", + " logger.error('Failed to create subscription')\n", + "\n", + " # Any processing here\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: %s', str(data))\n", + " logger.info('v2x_resource: ' + v2x_resource)\n", + " if sub_id is not None:\n", + " logger.info('sub_id: ' + sub_id)\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Stop notification server\n", + " stop_notification_server(httpd)\n", + "\n", + " # Delete the V2X message subscritpion\n", + " delete_mec_subscription(v2x_resource)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create our fourth MEC application: how to use V2X QoS Prediction\n", + "\n", + "The MEC Sanbox V2X QoS Prediction is based on a grid Map of Monaco City where areas are categorized into residential, commercial and coastal. \n", + "PoAs (Point Of Access) are categorized depending on where they lie in each grid. \n", + "Each category has its own traffic load patterns which are pre-determin. The V2X QoS Prediction) will give more accurate values of RSRP and RSRQ based on the diurnal traffic patterns for each. The network scenario named \"4g-5g-v2x-macro\" must be used to get access to the V2X QoS Prediction feature.\n", + "\n", + "**Note:** The MEC Sanbox V2X QoS Prediction is enabled when the PredictedQos.routes.routeInfo.time attribute is present in the request (see ETSI GS MEC 030 V3.2.1 (2024-02) Clause 6.2.6 Type: Preditecd QoS)\n", + "\n", + "Limitations:\n", + "* The Location Granularity is currently not being validated as RSRP/RSRP calculations are done at the exact location provided by the user.\n", + "* Time Granularity is currently not supported by the Prediction Function (design limitations of the minimal, emulated, pre-determined traffic prediction)\n", + "* Upper limit on the number of elements (10 each) in the routes and routeInfo structures (arrays) to not affect user experience and respoy\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The table below describes the excepted Qos with and without the prediction model in deiffrent area and at different time.\n", + "\n", + "| Location | Time | PoA | Category | Status | QoS without Prediction Model | QoS with Prediction Model | Expected |\n", + "| \t | (Unix time in sec) | Standard (GMT) | | | | RSRP | RSRQ | RSRP | RSRQ | |\n", + "| ------------------- | ----------- | -------------- | ---------------- | ----------- | ------------- | -------------- | ----------- | ----------- | ----------- | -------- |\n", + "| 43.729416,7.414853 | 1653295620 | 08:47:00 | 4g-macro-cell-2 | Residential | Congested | 63 | 21 | 60 | 20 | Yes |\n", + "| 43.732456,7.418417 | 1653299220 | 09:47:00 | 4g-macro-cell-3 | Residential | Not Congested | 55 | 13 | 55 | 13 | Yes |\n", + "| 43.73692,7.4209256 | 1653302820 | 10:47:00 | 4g-macro-cell-6 | Coastal | Not Congested | 68 | 26 | 68 | 26 | Yes |\n", + "| 43.738007,7.4230533 | 1653305220 | 11:27:00 | 4g-macro-cell-6 | Coastal | Not Congested | 55 | 13 | 55 | 13 | Yes |\n", + "| 43.739685,7.424881 | 1653308820 | 12:27:00 | 4g-macro-cell-7 | Commercial | Congested | 63 | 21 | 40 | 13 | Yes |\n", + "| 43.74103,7.425759 | 1653312600 | 13:30:00 | 4g-macro-cell-7 | Commercial | Congested | 56 | 14 | 40 | 8 | Yes |\n", + "| 43.74258,7.4277945 | 1653315900 | 14:25:00 | 4g-macro-cell-8 | Coastal | Congested | 59 | 17 | 47 | 13 | Yes |\n", + "| 43.744972,7.4295254 | 1653318900 | 15:15:00 | 4g-macro-cell-8 | Coastal | Congested | 53 | 11 | 40 | 5 | Yes |\n", + "| 43.74773,7.4320855 | 1653322500 | 16:15:00 | 5g-small-cell-14 | Commercial | Congested | 78 | 69 | 60 | 53 | Yes |\n", + "| 43.749264,7.435894 | 1653329700 | 18:15:00 | 5g-small-cell-20 | Commercial | Not Congested | 84 | 72 | 84 | 72 | Yes |\t72\t84\t72\tYes\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The image below illustrate the table above: [here](images/V2X Predicted QoS.jpg).\n", + "\n", + "Here is an example of a basic V2X predicted QoS request based on two point in path at 8am in Residential area:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```json\n", + "{\n", + " \"predictionTarget\": \"SINGLE_UE_PREDICTION\",\n", + " \"timeGranularity\": null,\n", + " \"locationGranularity\": \"30\",\n", + " \"routes\": [\n", + " {\n", + " \"routeInfo\": [\n", + " {\n", + " \"location\": {\n", + " \"geoArea\": {\n", + " \"latitude\": 43.729416,\n", + " \"longitude\": 7.414853\n", + " }\n", + " },\n", + " \"time\": {\n", + " \"nanoSeconds\": 0,\n", + " \"seconds\": 1653295620\n", + " }\n", + " },\n", + " {\n", + " \"location\": {\n", + " \"geoArea\": {\n", + " \"latitude\": 43.732456,\n", + " \"longitude\": 7.418417\n", + " }\n", + " },\n", + " \"time\": {\n", + " \"nanoSeconds\": 0,\n", + " \"seconds\": 1653299220\n", + " ]\n", + " }\n", + " ]\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let first create the required types before to prepare a V2X Predicted QoS request based on the JSON above.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Routes(object):\n", + " swagger_types = {'_route_info': 'list[RouteInfo]'}\n", + " attribute_map = {'_route_info': 'routeInfo'}\n", + " def __init__(self, route_info:list): # noqa: E501\n", + " self._route_info = None\n", + " self.route_info = route_info\n", + " @property\n", + " def route_info(self):\n", + " return self._route_info\n", + " @route_info.setter\n", + " def route_info(self, route_info):\n", + " if route_info is None:\n", + " raise ValueError(\"Invalid value for `route_info`, must not be `None`\") # noqa: E501\n", + " self._route_info = route_info\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(Routes, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, Routes):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class LocationInfo(object):\n", + " swagger_types = {'_ecgi': 'Ecgi', '_geo_area': 'LocationInfoGeoArea'}\n", + " attribute_map = {'_ecgi': 'ecgi', '_geo_area': 'geoArea'}\n", + " def __init__(self, ecgi=None, geo_area=None): # noqa: E501\n", + " self._ecgi = None\n", + " self._geo_area = None\n", + " self.discriminator = None\n", + " if ecgi is not None:\n", + " self.ecgi = ecgi\n", + " if geo_area is not None:\n", + " self.geo_area = geo_area\n", + " @property\n", + " def ecgi(self):\n", + " return self._ecgi\n", + " @ecgi.setter\n", + " def ecgi(self, ecgi):\n", + " self._ecgi = ecgi\n", + " @property\n", + " def geo_area(self):\n", + " return self._geo_area\n", + " @geo_area.setter\n", + " def geo_area(self, geo_area):\n", + " self._geo_area = geo_area\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(LocationInfo, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, LocationInfo):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class RouteInfo(object):\n", + " swagger_types = {'_location': 'LocationInfo', '_time_stamp': 'timeStamp'}\n", + " attribute_map = {'_location': 'location', '_time_stamp': 'time'}\n", + " def __init__(self, location:LocationInfo, time_stamp=None): # noqa: E501\n", + " self._location = None\n", + " self.location = location\n", + " self._time_stamp = None\n", + " if time_stamp is not None:\n", + " self.time_stamp = time_stamp\n", + " @property\n", + " def location(self):\n", + " return self._location\n", + " @location.setter\n", + " def location(self, location):\n", + " if location is None:\n", + " raise ValueError(\"Invalid value for `location`, must not be `None`\") # noqa: E501\n", + " self._location = location\n", + " @property\n", + " def time_stamp(self):\n", + " return self._time_stamp\n", + " @time_stamp.setter\n", + " def time_stamp(self, time_stamp):\n", + " self._time_stamp = time_stamp\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(RouteInfo, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, RouteInfo):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class LocationInfoGeoArea(object):\n", + " swagger_types = {'_latitude': 'float', '_longitude': 'float'}\n", + " attribute_map = {'_latitude': 'latitude', '_longitude': 'longitude'}\n", + " def __init__(self, latitude, longitude): # noqa: E501\n", + " self._latitude = None\n", + " self._longitude = None\n", + " self.discriminator = None\n", + " if latitude is not None:\n", + " self.latitude = latitude\n", + " if longitude is not None:\n", + " self.longitude = longitude\n", + " @property\n", + " def latitude(self):\n", + " return self._latitude\n", + " @latitude.setter\n", + " def latitude(self, latitude):\n", + " if latitude is None:\n", + " raise ValueError(\"Invalid value for `latitude`, must not be `None`\") # noqa: E501\n", + " self._latitude = latitude\n", + " @property\n", + " def longitude(self):\n", + " return self._longitude\n", + " @longitude.setter\n", + " def longitude(self, longitude):\n", + " if longitude is None:\n", + " raise ValueError(\"Invalid value for `longitude`, must not be `None`\") # noqa: E501\n", + " self._longitude = longitude\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(LocationInfoGeoArea, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, LocationInfoGeoArea):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class Ecgi(object):\n", + " swagger_types = {'_cellId': 'CellId', '_plmn': 'Plmn'}\n", + " attribute_map = {'_cellId': 'cellId', '_plmn': 'plmn'}\n", + " def __init__(self, cellId=None, plmn=None): # noqa: E501\n", + " self._cellId = None\n", + " self._plmn = None\n", + " self.discriminator = None\n", + " if cellId is not None:\n", + " self.cellId = cellId\n", + " if plmn is not None:\n", + " self.plmn = plmn\n", + " @property\n", + " def cellId(self):\n", + " return self._cellId\n", + " @cellId.setter\n", + " def cellId(self, cellId):\n", + " self._cellId = cellId\n", + " @property\n", + " def plmn(self):\n", + " return self._plmn\n", + " @plmn.setter\n", + " def plmn(self, plmn):\n", + " self._plmn = plmn\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(Ecgi, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, Ecgi):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class CellId(object):\n", + " swagger_types = {'_cellId': 'str'}\n", + " attribute_map = {'_cellId': 'cellId'}\n", + " def __init__(self, cellId): # noqa: E501\n", + " self._cellId = None\n", + " self.cellId = cellId\n", + " @property\n", + " def cellId(self):\n", + " return self._cellId\n", + " @cellId.setter\n", + " def cellId(self, cellId):\n", + " if cellId is None:\n", + " raise ValueError(\"Invalid value for `cellId`, must not be `None`\") # noqa: E501\n", + " self._cellId = cellId\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(CellId, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, CellId):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class Plmn(object):\n", + " swagger_types = {'_mcc': 'str', '_mnc': 'str'}\n", + " attribute_map = {'_mcc': 'mcc', '_mnc': 'mnc'}\n", + " def __init__(self, mcc:str, mnc:str): # noqa: E501\n", + " self.discriminator = None\n", + " self._mcc = None\n", + " self._mnc = None\n", + " self.mcc = mcc\n", + " self.mnc = mnc\n", + " @property\n", + " def mcc(self):\n", + " return self._mcc\n", + " @mcc.setter\n", + " def kpi_nmccame(self, mcc):\n", + " if mcc is None:\n", + " raise ValueError(\"Invalid value for `mcc`, must not be `None`\") # noqa: E501\n", + " self._mcc = mcc\n", + " @property\n", + " def mnc(self):\n", + " return self._mnc\n", + " @mnc.setter\n", + " def kpi_nmccame(self, mnc):\n", + " if mnc is None:\n", + " raise ValueError(\"Invalid value for `mnc`, must not be `None`\") # noqa: E501\n", + " self._mnc = mnc\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(Plmn, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, Plmn):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class QosKpi(object):\n", + " swagger_types = {'_kpi_name': 'str', '_kpi_value': 'str', '_confidence': 'str'}\n", + " attribute_map = {'_kpi_name': 'kpiName', '_kpi_value': 'kpiValue', '_confidence': 'Confidence'}\n", + " def __init__(self, kpi_name:str, kpi_value:str, confidence=None): # noqa: E501\n", + " self._kpi_name = None\n", + " self._kpi_value = None\n", + " self._confidence = None\n", + " self.kpi_name = kpi_name\n", + " self.kpi_value = kpi_value\n", + " if confidence is not None:\n", + " self.confidences = confidence\n", + " @property\n", + " def kpi_name(self):\n", + " return self._kpi_name\n", + " @kpi_name.setter\n", + " def kpi_name(self, kpi_name):\n", + " if kpi_name is None:\n", + " raise ValueError(\"Invalid value for `kpi_name`, must not be `None`\") # noqa: E501\n", + " self._kpi_name = kpi_name\n", + " @property\n", + " def kpi_value(self):\n", + " return self._kpi_value\n", + " @kpi_value.setter\n", + " def kpi_value(self, kpi_value):\n", + " if kpi_value is None:\n", + " raise ValueError(\"Invalid value for `kpi_value`, must not be `None`\") # noqa: E501\n", + " self._kpi_value = kpi_value\n", + " @property\n", + " def confidence(self):\n", + " return self._confidence\n", + " @confidence.setter\n", + " def confidence(self, confidence):\n", + " self._confidence = confidence\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(QosKpi, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, QosKpi):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class Stream(object):\n", + " swagger_types = {'_stream_id': 'str', '_qos_kpi': 'list[QosKpi]'}\n", + " attribute_map = {'_stream_id': 'streamId', '_qos_kpi': 'qosKpi'}\n", + " def __init__(self, stream_id:str, qos_kpi:list): # noqa: E501\n", + " self._stream_id = None\n", + " self._qos_kpi = None\n", + " self.stream_id = stream_id\n", + " self.qos_kpi = qos_kpi\n", + " @property\n", + " def stream_id(self):\n", + " return self._stream_id\n", + " @stream_id.setter\n", + " def stream_id(self, stream_id):\n", + " if stream_id is None:\n", + " raise ValueError(\"Invalid value for `stream_id`, must not be `None`\") # noqa: E501\n", + " self._stream_id = stream_id\n", + " @property\n", + " def qos_kpi(self):\n", + " return self._qos_kpi\n", + " @qos_kpi.setter\n", + " def qos_kpi(self, qos_kpi):\n", + " if qos_kpi is None:\n", + " raise ValueError(\"Invalid value for `qos_kpi`, must not be `None`\") # noqa: E501\n", + " self._qos_kpi = qos_kpi\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(Stream, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, Stream):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class Qos(object):\n", + " swagger_types = {'_stream': 'list[Stream]'}\n", + " attribute_map = {'_stream': 'stream'}\n", + " def __init__(self, stream:list): # noqa: E501\n", + " self._stream = None\n", + " self.stream = stream\n", + " @property\n", + " def stream(self):\n", + " return self._stream\n", + " @stream.setter\n", + " def stream(self, stream):\n", + " if stream is None:\n", + " raise ValueError(\"Invalid value for `stream`, must not be `None`\") # noqa: E501\n", + " self._stream = stream\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(Qos, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, Qos):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class PredictedQos(object):\n", + " swagger_types = {'_location_granularity': 'str', '_notice_period': 'timeStamp', '_prediction_area': 'PredictionArea', '_prediction_target': 'str', '_qos': 'Qos', '_routes': 'list[Routes]', '_time_granularity': 'timeStamp'}\n", + " attribute_map = {'_location_granularity': 'locationGranularity', '_notice_period': 'noticePeriod', '_prediction_area': 'predictionArea', '_prediction_target': 'predictionTarget', '_qos': 'qos', '_routes': 'routes', '_time_granularity': 'timeGranularity'}\n", + " def __init__(self, prediction_target:str, location_granularity:str, notice_period=None, time_granularity=None, prediction_area=None, routes=None, qos=None): # noqa: E501\n", + " self._prediction_target = None\n", + " self._time_granularity = None\n", + " self._location_granularity = None\n", + " self._notice_period = None\n", + " self._prediction_area = None\n", + " self._routes = None\n", + " self._qos = None\n", + " self._prediction_target = prediction_target\n", + " if time_granularity is not None:\n", + " self.time_granularity = time_granularity\n", + " self.location_granularity = location_granularity\n", + " if notice_period is not None:\n", + " self.notice_period = notice_period\n", + " if prediction_area is not None:\n", + " self.prediction_area = prediction_area\n", + " if routes is not None:\n", + " self.routes = routes\n", + " if qos is not None:\n", + " self.qos = qos\n", + " @property\n", + " def prediction_target(self):\n", + " return self._prediction_target\n", + " @prediction_target.setter\n", + " def prediction_target(self, prediction_target):\n", + " if prediction_target is None:\n", + " raise ValueError(\"Invalid value for `prediction_target`, must not be `None`\") # noqa: E501\n", + " self._prediction_target = prediction_target\n", + " @property\n", + " def time_granularity(self):\n", + " return self._time_granularity\n", + " @time_granularity.setter\n", + " def time_granularity(self, time_granularity):\n", + " self._time_granularity = time_granularity\n", + " @property\n", + " def location_granularity(self):\n", + " return self._location_granularity\n", + " @location_granularity.setter\n", + " def location_granularity(self, location_granularity):\n", + " if location_granularity is None:\n", + " raise ValueError(\"Invalid value for `location_granularity`, must not be `None`\") # noqa: E501\n", + " self._location_granularity = location_granularity\n", + " @property\n", + " def notice_period(self):\n", + " return self._notice_period\n", + " @notice_period.setter\n", + " def notice_period(self, notice_period):\n", + " self._notice_period = notice_period\n", + " @property\n", + " def prediction_area(self):\n", + " return self._prediction_area\n", + " @prediction_area.setter\n", + " def prediction_area(self, prediction_area):\n", + " self._prediction_area = prediction_area\n", + " @property\n", + " def routes(self):\n", + " return self._routes\n", + " @routes.setter\n", + " def routes(self, routes):\n", + " self._routes = routes\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(PredictedQos, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, PredictedQos):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is the V2X Prediscted QoS function.\n", + "\n", + "Reference: ETSI GS MEC 030 V3.2.1 (2024-02) Clause 5.5.5 Sending a request for journey-specific QoS predictions\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_qos_prediction(sandbox_name: str) -> object:\n", + " \"\"\"\n", + " Request to predictede QoS\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return The HTTP response, the HTTP response status and the HTTP response headers on success, None otherwise\n", + " :see ETSI GS MEC 030 V3.2.1 (2024-02) Clause 5.5.5 Sending a request for journey-specific QoS predictions\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> get_qos_prediction: sandbox_name: ' + sandbox_name)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/vis/v2/provide_predicted_qos'\n", + " logger.debug('send_uu_unicast_provisioning_info: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " # HTTP header `Accept`\n", + " header_params = {}\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # Body request\n", + " loc1 = LocationInfo(geo_area=LocationInfoGeoArea(latitude=43.729416, longitude=7.414853))\n", + " loc2 = LocationInfo(geo_area=LocationInfoGeoArea(latitude=43.732456, longitude=7.418417))\n", + " routeInfo1 = RouteInfo(loc1, timeStamp(nano_seconds=0, seconds=1653295620))\n", + " routeInfo2 = RouteInfo(loc2, timeStamp(nano_seconds=0, seconds=1653299220))\n", + " routesInfo = [routeInfo1, routeInfo2]\n", + " predictedQos = PredictedQos(prediction_target=\"SINGLE_UE_PREDICTION\", location_granularity=\"30\", routes=[Routes(routesInfo)])\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params=path_params, body=predictedQos, async_req=False)\n", + " return (result, status, headers)\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status, None)\n", + " # End of function send_uu_unicast_provisioning_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Grouping all together provides the process_main funtion.. The sequence is the following:\n", + "- Mec application setup\n", + "- V2X QoS request\n", + "- Mec application termination\n", + "\n", + "The expected response should be:\n", + "- RSRP: 55\n", + "- RSRQ: 13" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fourth sprint of our skeleton of our MEC application:\n", + " - Mec application setup\n", + " - V2X QoS request\n", + " - Mec application termination\n", + " \"\"\" \n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " sandbox_name, app_inst_id, sub_id = mec_app_setup()\n", + "\n", + " # QoS Prediction\n", + " (result, status, headers) = get_qos_prediction(sandbox_name)\n", + " if status != 200:\n", + " logger.error('Failed to get UU unicast provisioning information')\n", + " else:\n", + " logger.info('UU unicast provisioning information: result: %s', str(result.data))\n", + " \n", + " # Any processing here\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: %s', str(data))\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Our fith MEC application: how to create a new MEC Services\n", + "\n", + "The purpose of this MEC Service application is to provide a custom MEC service that can be use by other MEC applications. For the purpose of this tutorial, our MEC service is simulating some complex calculation based on a set of data provided by the MEC use. \n", + "We will use a second MEC application to exploit the features of our new MEC services.\n", + "\n", + "In this clause, we use the following functionalities provided by MEC-011:\n", + "- Register a new service\n", + "- Retrieve the list of the MEC services exposed by the MEC platform\n", + "- Check that our new MEC service is present in the list of the MEC platform services\n", + "- Execute a request to the MEC service\n", + "- Delete the newly created service\n", + "\n", + "**Note:** We will use a second MEC application to exploit the features of our new MEC services.\n", + "\n", + "**Reference:** ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.4 Service availability update and new service registration\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bases of the creation of a MEC service\n", + "\n", + "#### Introduction\n", + "\n", + "From the user perspective, a MEC service provides a set of endpoints which describe the interface of the MEC service (see [HTTP REST APIs \n", + "concepts](https://blog.postman.com/rest-api-examples/)). These endpoints come usually with a set of data structures used by the one or more endpoints.\n", + "\n", + "Our service is really basic: it provide one endpoint:\n", + "- GET /statistic/v1/quantity: it computes statistical quantities of a set of data (such as average, max, min, standard deviation)\n", + "\n", + "The body of this GET method is a list of datas:\n", + "```json\n", + "{\"time\":20180124,\"data1\":\"[1516752000,11590.6,11616.9,11590.4,11616.9,0.25202387,1516752060,11622.4,11651.7,11622.4,11644.6,1.03977764]\"}\n", + "```\n", + "\n", + "The response body is the list of statistical quantities:\n", + "```json\n", + "{\"time\":20180124,\"avg\": 0.0,\"max\": 0.0,\"min\": 0.0,\"stddev\": 0.0 }\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### MEC mechanisms to create a new service\n", + "\n", + "As described in ETSI GS MEC 011 Clause 5.2.4 Service availability update and new service registration, to create a new MEC service, the following information is required:\n", + "- A MEC Aplication instance: this is the MEC application providing the new MEC service (ETSI GS MEC 011 V3.2.1 Clause 8.2.6.3.4 POST)\n", + "- A ServiceInfo instance which describe the MEC service (ETSI GS MEC 011 V3.2.1 Clause 8.1.2.2 Type: ServiceInfo)\n", + "- As part of the ServiceInfo instance, a TransportInfo (ETSI GS MEC 011 V3.2.1 Clause 8.1.2.3 Type: TransportInfo) instance descibes the endpoints to use the MEC service\n", + "\n", + "When created and available, all the other MEC applications are notified about the existance of this MEC service." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### ServiceInfo data type\n", + "\n", + "The cell below describes the ServiceInfo data structure and its dependencies. It will be used to create our MEC servie.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class ServiceInfo(object):\n", + " swagger_types = {'ser_instance_id': 'str','ser_name': 'str','ser_category': 'CategoryRef','version': 'str','state': 'str','transport_id': 'str','transport_info': 'TransportInfo','serializer': 'string','scope_of_locality': 'LocalityType','consumed_local_only': 'bool','is_local': 'bool','liveness_interval': 'int','links': 'ServiceInfoLinks'}\n", + " attribute_map = {'ser_instance_id': 'serInstanceId','ser_name': 'serName','ser_category': 'serCategory','version': 'version','state': 'state','transport_id': 'transportId','transport_info': 'transportInfo','serializer': 'serializer','scope_of_locality': 'scopeOfLocality','consumed_local_only': 'consumedLocalOnly','is_local': 'isLocal','liveness_interval': 'livenessInterval','links': '_links'}\n", + " def __init__(self, ser_instance_id=None, ser_name=None, ser_category=None, version=None, state=None, transport_id=None, transport_info=None, serializer=None, scope_of_locality=None, consumed_local_only=None, is_local=None, liveness_interval=None, links=None): # noqa: E501\n", + " self._ser_instance_id = None\n", + " self._ser_name = None\n", + " self._ser_category = None\n", + " self._version = None\n", + " self._state = None\n", + " self._transport_id = None\n", + " self._transport_info = None\n", + " self._serializer = None\n", + " self._scope_of_locality = None\n", + " self._consumed_local_only = None\n", + " self._is_local = None\n", + " self._liveness_interval = None\n", + " self._links = None\n", + " self.discriminator = None\n", + " if ser_instance_id is not None:\n", + " self.ser_instance_id = ser_instance_id\n", + " self.ser_name = ser_name\n", + " if ser_category is not None:\n", + " self.ser_category = ser_category\n", + " self.version = version\n", + " self.state = state\n", + " if transport_id is not None:\n", + " self.transport_id = transport_id\n", + " self.transport_info = transport_info\n", + " self.serializer = serializer\n", + " if scope_of_locality is not None:\n", + " self.scope_of_locality = scope_of_locality\n", + " if consumed_local_only is not None:\n", + " self.consumed_local_only = consumed_local_only\n", + " if is_local is not None:\n", + " self.is_local = is_local\n", + " if liveness_interval is not None:\n", + " self.liveness_interval = liveness_interval\n", + " if links is not None:\n", + " self.links = links\n", + " @property\n", + " def ser_instance_id(self):\n", + " return self._ser_instance_id\n", + " @ser_instance_id.setter\n", + " def ser_instance_id(self, ser_instance_id):\n", + " self._ser_instance_id = ser_instance_id\n", + " @property\n", + " def ser_name(self):\n", + " return self._ser_name\n", + " @ser_name.setter\n", + " def ser_name(self, ser_name):\n", + " if ser_name is None:\n", + " raise ValueError(\"Invalid value for `ser_name`, must not be `None`\") # noqa: E501\n", + " self._ser_name = ser_name\n", + " @property\n", + " def ser_category(self):\n", + " return self._ser_category\n", + " @ser_category.setter\n", + " def ser_category(self, ser_category):\n", + " self._ser_category = ser_category\n", + " @property\n", + " def version(self):\n", + " return self._version\n", + " @version.setter\n", + " def version(self, version):\n", + " if version is None:\n", + " raise ValueError(\"Invalid value for `version`, must not be `None`\") # noqa: E501\n", + " self._version = version\n", + " @property\n", + " def state(self):\n", + " return self._state\n", + " @state.setter\n", + " def state(self, state):\n", + " if state is None:\n", + " raise ValueError(\"Invalid value for `state`, must not be `None`\") # noqa: E501\n", + " self._state = state\n", + " @property\n", + " def transport_id(self):\n", + " return self._transport_id\n", + " @transport_id.setter\n", + " def transport_id(self, transport_id):\n", + " self._transport_id = transport_id\n", + " @property\n", + " def transport_info(self):\n", + " return self._transport_info\n", + " @transport_info.setter\n", + " def transport_info(self, transport_info):\n", + " if transport_info is None:\n", + " raise ValueError(\"Invalid value for `transport_info`, must not be `None`\") # noqa: E501\n", + " self._transport_info = transport_info\n", + " @property\n", + " def serializer(self):\n", + " return self._serializer\n", + " @serializer.setter\n", + " def serializer(self, serializer):\n", + " if serializer is None:\n", + " raise ValueError(\"Invalid value for `serializer`, must not be `None`\") # noqa: E501\n", + " self._serializer = serializer\n", + " @property\n", + " def scope_of_locality(self):\n", + " return self._scope_of_locality\n", + " @scope_of_locality.setter\n", + " def scope_of_locality(self, scope_of_locality):\n", + " self._scope_of_locality = scope_of_locality\n", + " @property\n", + " def consumed_local_only(self):\n", + " return self._consumed_local_only\n", + " @consumed_local_only.setter\n", + " def consumed_local_only(self, consumed_local_only):\n", + " self._consumed_local_only = consumed_local_only\n", + " @property\n", + " def is_local(self):\n", + " return self._is_local\n", + " @is_local.setter\n", + " def is_local(self, is_local):\n", + " self._is_local = is_local\n", + " @property\n", + " def liveness_interval(self):\n", + " return self._liveness_interval\n", + " @liveness_interval.setter\n", + " def liveness_interval(self, liveness_interval):\n", + " self._liveness_interval = liveness_interval\n", + " @property\n", + " def links(self):\n", + " return self._links\n", + " @links.setter\n", + " def links(self, links):\n", + " self._links = links\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(ServiceInfo, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, ServiceInfo):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class CategoryRef(object):\n", + " swagger_types = {'href': 'str','id': 'str','name': 'str','version': 'str'}\n", + " attribute_map = {'href': 'href','id': 'id','name': 'name','version': 'version'}\n", + " def __init__(self, href=None, id=None, name=None, version=None): # noqa: E501\n", + " self._href = None\n", + " self._id = None\n", + " self._name = None\n", + " self._version = None\n", + " self.discriminator = None\n", + " self.href = href\n", + " self.id = id\n", + " self.name = name\n", + " self.version = version\n", + " @property\n", + " def href(self):\n", + " return self._href\n", + " @href.setter\n", + " def href(self, href):\n", + " if href is None:\n", + " raise ValueError(\"Invalid value for `href`, must not be `None`\") # noqa: E501\n", + " self._href = href\n", + " @property\n", + " def id(self):\n", + " return self._id\n", + " @id.setter\n", + " def id(self, id):\n", + " if id is None:\n", + " raise ValueError(\"Invalid value for `id`, must not be `None`\") # noqa: E501\n", + " self._id = id\n", + " @property\n", + " def name(self):\n", + " return self._name\n", + " @name.setter\n", + " def name(self, name):\n", + " if name is None:\n", + " raise ValueError(\"Invalid value for `name`, must not be `None`\") # noqa: E501\n", + " self._name = name\n", + " @property\n", + " def version(self):\n", + " return self._version\n", + " @version.setter\n", + " def version(self, version):\n", + " if version is None:\n", + " raise ValueError(\"Invalid value for `version`, must not be `None`\") # noqa: E501\n", + " self._version = version\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(CategoryRef, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, CategoryRef):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class TransportInfo(object):\n", + " swagger_types = {\n", + " 'id': 'str','name': 'str','description': 'str','type': 'str','protocol': 'str','version': 'str','endpoint': 'OneOfTransportInfoEndpoint','security': 'SecurityInfo','impl_specific_info': 'str'}\n", + " attribute_map = {'id': 'id','name': 'name','description': 'description','type': 'type','protocol': 'protocol','version': 'version','endpoint': 'endpoint','security': 'security','impl_specific_info': 'implSpecificInfo'}\n", + " def __init__(self, id=None, name=None, description=None, type=None, protocol=None, version=None, endpoint=None, security=None, impl_specific_info=None): # noqa: E501\n", + " self._id = None\n", + " self._name = None\n", + " self._description = None\n", + " self._type = None\n", + " self._protocol = None\n", + " self._version = None\n", + " self._endpoint = None\n", + " self._security = None\n", + " self._impl_specific_info = None\n", + " self.discriminator = None\n", + " self.id = id\n", + " self.name = name\n", + " if description is not None:\n", + " self.description = description\n", + " self.type = type\n", + " self.protocol = protocol\n", + " self.version = version\n", + " self.endpoint = endpoint\n", + " self.security = security\n", + " if impl_specific_info is not None:\n", + " self.impl_specific_info = impl_specific_info\n", + " @property\n", + " def id(self):\n", + " return self._id\n", + " @id.setter\n", + " def id(self, id):\n", + " if id is None:\n", + " raise ValueError(\"Invalid value for `id`, must not be `None`\") # noqa: E501\n", + " self._id = id\n", + " @property\n", + " def name(self):\n", + " return self._name\n", + " @name.setter\n", + " def name(self, name):\n", + " if name is None:\n", + " raise ValueError(\"Invalid value for `name`, must not be `None`\") # noqa: E501\n", + " self._name = name\n", + " @property\n", + " def description(self):\n", + " return self._description\n", + " @description.setter\n", + " def description(self, description):\n", + " self._description = description\n", + " @property\n", + " def type(self):\n", + " return self._type\n", + " @type.setter\n", + " def type(self, type):\n", + " if type is None:\n", + " raise ValueError(\"Invalid value for `type`, must not be `None`\") # noqa: E501\n", + " self._type = type\n", + " @property\n", + " def protocol(self):\n", + " return self._protocol\n", + " @protocol.setter\n", + " def protocol(self, protocol):\n", + " if protocol is None:\n", + " raise ValueError(\"Invalid value for `protocol`, must not be `None`\") # noqa: E501\n", + " self._protocol = protocol\n", + " @property\n", + " def version(self):\n", + " return self._version\n", + " @version.setter\n", + " def version(self, version):\n", + " if version is None:\n", + " raise ValueError(\"Invalid value for `version`, must not be `None`\") # noqa: E501\n", + " self._version = version\n", + " @property\n", + " def endpoint(self):\n", + " return self._endpoint\n", + " @endpoint.setter\n", + " def endpoint(self, endpoint):\n", + " if endpoint is None:\n", + " raise ValueError(\"Invalid value for `endpoint`, must not be `None`\") # noqa: E501\n", + " self._endpoint = endpoint\n", + " @property\n", + " def security(self):\n", + " return self._security\n", + " @security.setter\n", + " def security(self, security):\n", + " if security is None:\n", + " raise ValueError(\"Invalid value for `security`, must not be `None`\") # noqa: E501\n", + " self._security = security\n", + " @property\n", + " def impl_specific_info(self):\n", + " return self._impl_specific_info\n", + " @impl_specific_info.setter\n", + " def impl_specific_info(self, impl_specific_info):\n", + " self._impl_specific_info = impl_specific_info\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(TransportInfo, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, TransportInfo):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class SecurityInfo(object):\n", + " swagger_types = {'o_auth2_info': 'SecurityInfoOAuth2Info'}\n", + " attribute_map = {'o_auth2_info': 'oAuth2Info'}\n", + " def __init__(self, o_auth2_info=None): # noqa: E501\n", + " self._o_auth2_info = None\n", + " self.discriminator = None\n", + " if o_auth2_info is not None:\n", + " self.o_auth2_info = o_auth2_info\n", + " @property\n", + " def o_auth2_info(self):\n", + " return self._o_auth2_info\n", + " @o_auth2_info.setter\n", + " def o_auth2_info(self, o_auth2_info):\n", + " self._o_auth2_info = o_auth2_info\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(SecurityInfo, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, SecurityInfo):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class SecurityInfoOAuth2Info(object):\n", + " swagger_types = {'grant_types': 'list[str]','token_endpoint': 'str'}\n", + " attribute_map = {'grant_types': 'grantTypes','token_endpoint': 'tokenEndpoint'}\n", + " def __init__(self, grant_types=None, token_endpoint=None): # noqa: E501\n", + " self._grant_types = None\n", + " self._token_endpoint = None\n", + " self.discriminator = None\n", + " self.grant_types = grant_types\n", + " self.token_endpoint = token_endpoint\n", + " @property\n", + " def grant_types(self):\n", + " return self._grant_types\n", + " @grant_types.setter\n", + " def grant_types(self, grant_types):\n", + " if grant_types is None:\n", + " raise ValueError(\"Invalid value for `grant_types`, must not be `None`\") # noqa: E501\n", + " self._grant_types = grant_types\n", + " @property\n", + " def token_endpoint(self):\n", + " return self._token_endpoint\n", + " @token_endpoint.setter\n", + " def token_endpoint(self, token_endpoint):\n", + " if token_endpoint is None:\n", + " raise ValueError(\"Invalid value for `token_endpoint`, must not be `None`\") # noqa: E501\n", + " self._token_endpoint = token_endpoint\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(SecurityInfoOAuth2Info, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, SecurityInfoOAuth2Info):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class OneOfTransportInfoEndpoint(object):\n", + " swagger_types = {}\n", + " attribute_map = {}\n", + " def __init__(self): # noqa: E501\n", + " self.discriminator = None\n", + " @property\n", + " def uris(self):\n", + " return self._uris\n", + " @uris.setter\n", + " def uris(self, uris):\n", + " self._uris = uris\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(OneOfappInstanceIdServicesBody, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, OneOfTransportInfoEndpoint):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class EndPointInfoUris(object):\n", + " swagger_types = {'_uris': 'list[str]'}\n", + " attribute_map = {'_uris': 'uris'}\n", + " def __init__(self, uris:list): # noqa: E501\n", + " self._uris = None\n", + " self.uris = uris\n", + " @property\n", + " def uris(self):\n", + " return self._uris\n", + " @uris.setter\n", + " def uris(self, uris):\n", + " self._uris = uris\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(EndPointInfoUris, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, EndPointInfoUris):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class EndPointInfoFqdn(object):\n", + " swagger_types = {'_fqdn': 'list[str]'}\n", + " attribute_map = {'_fqdn': 'fqdn'}\n", + " def __init__(self, fqdn:list): # noqa: E501\n", + " self._fqdn = None\n", + " self.fqdn = fqdn\n", + " @property\n", + " def fqdn(self):\n", + " return self._fqdn\n", + " @fqdn.setter\n", + " def fqdn(self, fqdn):\n", + " self._fqdn = fqdn\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(EndPointInfoFqdn, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, EndPointInfoFqdn):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class EndPointInfoAddress(object):\n", + " swagger_types = {'_host': 'str', '_port': 'int'}\n", + " attribute_map = {'_host': 'host', '_port': 'port'}\n", + " def __init__(self, host:str, port:list): # noqa: E501\n", + " self._host = None\n", + " self._port = None\n", + " self.host = host\n", + " self.port = port\n", + " @property\n", + " def host(self):\n", + " return self.host\n", + " @host.setter\n", + " def host(self, host):\n", + " self._host = host\n", + " @property\n", + " def port(self):\n", + " return self._port\n", + " @port.setter\n", + " def port(self, port):\n", + " self._port = qosport_kpi\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(EndPointInfoAddress, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, EndPointInfoAddress):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class EndPointInfoAddresses(object):\n", + " swagger_types = {'_addresses': 'list[EndPointInfoAddress]'}\n", + " attribute_map = {'_addresses': 'addresses'}\n", + " def __init__(self, addresses:list): # noqa: E501\n", + " self._addresses = None\n", + " self.addresses = addresses\n", + " @property\n", + " def addresses(self):\n", + " return self._addresses\n", + " @addresses.setter\n", + " def addresses(self, addresses):\n", + " self._addresses = addresses\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(lambda x: x.to_dict() if hasattr(x, 'to_dict') else x,value))\n", + " elif hasattr(value, 'to_dict'):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], 'to_dict') else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(EndPointInfoAddresses, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, EndPointInfoAddresses):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n", + "\n", + "class EndPointInfoAlternative(object):\n", + " swagger_types = {'alternative': 'object'}\n", + " attribute_map = {'alternative': 'alternative'}\n", + " def __init__(self, alternative=None): # noqa: E501\n", + " self._alternative = None\n", + " self.discriminator = None\n", + " self.alternative = alternative\n", + " @property\n", + " def alternative(self):\n", + " return self._alternative\n", + " @alternative.setter\n", + " def alternative(self, alternative):\n", + " if alternative is None:\n", + " raise ValueError(\"Invalid value for `alternative`, must not be `None`\") # noqa: E501\n", + " self._alternative = alternative\n", + " def to_dict(self):\n", + " result = {}\n", + " for attr, _ in six.iteritems(self.swagger_types):\n", + " value = getattr(self, attr)\n", + " if isinstance(value, list):\n", + " result[attr] = list(map(\n", + " lambda x: x.to_dict() if hasattr(x, \"to_dict\") else x,\n", + " value\n", + " ))\n", + " elif hasattr(value, \"to_dict\"):\n", + " result[attr] = value.to_dict()\n", + " elif isinstance(value, dict):\n", + " result[attr] = dict(map(\n", + " lambda item: (item[0], item[1].to_dict())\n", + " if hasattr(item[1], \"to_dict\") else item,\n", + " value.items()\n", + " ))\n", + " else:\n", + " result[attr] = value\n", + " if issubclass(EndPointInfoAlternative, dict):\n", + " for key, value in self.items():\n", + " result[key] = value\n", + " return result\n", + " def to_str(self):\n", + " return pprint.pformat(self.to_dict())\n", + " def __repr__(self):\n", + " return self.to_str()\n", + " def __eq__(self, other):\n", + " if not isinstance(other, EndPointInfoAlternative):\n", + " return False\n", + " return self.__dict__ == other.__dict__\n", + " def __ne__(self, other):\n", + " return not self == other\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create an application MEC service\n", + "\n", + "The function below is creating an application MEC services\n", + "\n", + "**Note:** This is call application MEC service in opposition of a standardized MEC service exposed by the MEC Sanbox such as MEC 013, MEC 030...\n", + "\n", + "**Reference:** ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.2.6.3.4 POST" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def create_mec_service(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> object:\n", + " \"\"\"\n", + " Request to create a new application MEC service\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " :return The HTTP response, the response status and the headers on success, None otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.2.6.3.4 POST\n", + " \"\"\"\n", + " global MEC_PLTF, CALLBACK_URI, logger, service_api\n", + "\n", + " logger.debug('>>> create_mec_service')\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_service_mgmt/v1/applications/{app_inst_id}/services'\n", + " logger.debug('create_mec_service: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " # HTTP header `Accept`\n", + " header_params = {}\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # Body request\n", + " callback = CALLBACK_URI + '/statistic/v1/quantity'\n", + " transport_info = TransportInfo(id=str(uuid.uuid4()), name='HTTP REST API', type='REST_HTTP', protocol='HTTP', version='2.0', security=SecurityInfo(), endpoint=OneOfTransportInfoEndpoint())\n", + " transport_info.endpoint.uris=[EndPointInfoUris(callback)]\n", + " category_ref = CategoryRef(href=callback, id=str(uuid.uuid4()), name='Demo', version='1.0.0')\n", + " appServiceInfo = ServiceInfo(ser_name='demo6 MEC Service', ser_category=category_ref, version='1.0.0', state='ACTIVE',transport_info=transport_info, serializer='JSON')\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params=path_params, body=appServiceInfo, async_req=False)\n", + " return (result, status, headers['Location'])\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status, None)\n", + " # End of function create_mec_service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete an application MEC service\n", + "\n", + "The function below is deleting an application MEC services.\n", + "\n", + "**Reference:** ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.2.7.3.5 DELETE" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def delete_mec_service(resource_url: str) -> int:\n", + " \"\"\"\n", + " Request to create a new application MEC service\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " :return 0 on success, -1 otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.2.7.3.5 DELETE\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> delete_mec_subscription: resource_url: ' + resource_url)\n", + " try:\n", + " res = urllib3.util.parse_url(resource_url)\n", + " if res is None:\n", + " logger.error('delete_mec_subscription: Failed to paerse URL')\n", + " return -1\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " service_api.call_api(res.path, 'DELETE', header_params=header_params, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function delete_mec_service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together\n", + "\n", + "The sequence is the following:\n", + "- Mec application setup\n", + "- Create new MEC service\n", + "- Send a request to our MEC service\n", + "- Delete newly created MEC service\n", + "- Mec application termination\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fith sprint of our skeleton of our MEC application:\n", + " - Mec application setup\n", + " - Create new MEC service\n", + " - Send a request to our MEC service\n", + " - Delete newly created MEC service\n", + " - Mec application termination\n", + " \"\"\" \n", + " global LISTENER_IP, LISTENER_PORT, CALLBACK_URI, logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Start notification server in a daemonized thread\n", + " httpd = start_notification_server()\n", + "\n", + " # Setup the MEC application\n", + " sandbox_name, app_inst_id, sub_id = mec_app_setup()\n", + "\n", + " # Create the MEC service\n", + " result, status, mec_service_resource = create_mec_service(sandbox_name, app_inst_id)\n", + " if status != 201:\n", + " logger.error('Failed to create MEC service')\n", + " else:\n", + " logger.info('mec_service_resource: %s', mec_service_resource)\n", + "\n", + " # Send a request to our MEC service\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: %s', str(data))\n", + " logger.info('=============> Execute the command: curl --request GET %s/statistic/v1/quantity --header \"Accept: application/json\" --data \\'{\"time\":20180124,\"data1\":\"[1516752000,11590.6,11616.9,11590.4,11616.9,0.25202387,1516752060,11622.4,11651.7,11622.4,11644.6,1.03977764]\"}\\'', CALLBACK_URI)\n", + " time.sleep(60)\n", + "\n", + " # Stop notification server\n", + " stop_notification_server(httpd)\n", + "\n", + " # Delete the MEC servce\n", + " delete_mec_service(mec_service_resource)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Support of CAPIF (3GPP TS 29.222: 3rd Generation Partnership Project; Technical Specification Group Core Network and Terminals; Common API Framework for 3GPP Northbound APIs)\n", + "\n", + "MEC-CAPIF support is described in ETSI GS MEC 011 (V3.2.1) Clause 9 [4]\n", + "\n", + "The sample code below illustrates the usage of MEC-CAPI endpoints:\n", + "- /service-apis/v1/allServiceAPIs\n", + "- /published-apis/v1/{apfId}/service-apis\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Getting all MEC services\n", + "\n", + "The code below illustrates how to use CAPIF '/service-apis/v1/allServiceAPIs' endpoint to retrieve the complete list of available MEC services.\n", + "\n", + "**Reference:** ETSI GS MEC 011 (V3.2.1) Clause 9.2.3 Resource: All published service APIs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def capif_get_all_mec_services(sandbox_name: str):\n", + " \"\"\"\n", + " To retrieves the MEC services using CAPIF endpoint\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return The HTTP response and the response status on success, None otherwise\n", + " :see ETSI GS MEC 011 (V3.2.1) Clause 9\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> capif_get_all_mec_services: ' + sandbox_name)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/service-apis/v1/allServiceAPIs'\n", + " logger.debug('capif_get_all_mec_services: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, async_req=False)\n", + " return (result, status)\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status)\n", + " # End of capif_get_all_mec_services function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Getting MEC services for a specified MEC application instance ID\n", + "\n", + "The code below illustrates how to use CAPIF '/published-apis/v1/{apfId}/service-apis' endpoint to retrieve the complete list of MEC services for a specified MEC application instance ID (apfid).\n", + "\n", + "**Reference:** ETSI GS MEC 011 (V3.2.1) Table 9.2.4.2-1: Profiling of the URI variables" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def capif_get_mec_services(sandbox_name: str, apfId: str):\n", + " \"\"\"\n", + " To retrieves the MEC services using CAPIF endpoint\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param apfId: The identifier of the entity that registers the service API\n", + " :return The HTTP response and the response status on success, None otherwise\n", + " :see ETSI GS MEC 011 (V3.2.1) Clause 9\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> capif_get_all_mec_services: ' + sandbox_name)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/published-apis/v1/{apfId}/service-apis'\n", + " logger.debug('capif_get_mec_services: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['apfId'] = apfId\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, async_req=False)\n", + " return (result, status)\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status)\n", + " # End of capif_get_mec_services function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting evrythong together\n", + "\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "\n", + "- Login\n", + "- Activate a network scenario\n", + "- Create an application instance identifier\n", + "- Retrieve all MEC services (/service-apis/v1/allServiceAPIs)\n", + "- Retrieve MEC services for the newly created application instance identifier\n", + "- Delete our application instance identifier\n", + "- Deactivate a network scenario\n", + "- Logout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This code illustrates the usage of MEC-CAPI endpoints:\n", + " - Login\n", + " - Activate a network scenario\n", + " - Create an application instance identifier\n", + " - Retrieve all MEC services (/service-apis/v1/allServiceAPIs)\n", + " - Retrieve MEC services for the newly created application instance identifier\n", + " - Delete our application instance identifier\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " \"\"\" \n", + " global LISTENER_IP, LISTENER_PORT, CALLBACK_URI, logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " sandbox_name, app_inst_id, sub_id = mec_app_setup()\n", + "\n", + " # Get the list of the MEC sevices using CAPIF endpoints\n", + " result, status = capif_get_all_mec_services(sandbox_name)\n", + " if status != 200:\n", + " logger.error('Failed to get the list of MEC services using CAPIF endpoint')\n", + " else:\n", + " logger.info('capif_get_all_mec_services: ' + str(result.data))\n", + "\n", + " # Get the list of the MEC sevices for our AppInstanceId using CAPIF endpoints\n", + " result, status = capif_get_mec_services(sandbox_name, app_inst_id.id)\n", + " if status != 200:\n", + " logger.error('Failed to get the list of MEC services for our AppInstanceId using CAPIF endpoint')\n", + " else:\n", + " logger.info('capif_get_mec_services: ' + str(result.data))\n", + "\n", + " # In the previous request, the list of services for our AppInstanceId is empty.\n", + " # Let's create a new MEC service\n", + " result, status, mec_service_resource = create_mec_service(sandbox_name, app_inst_id)\n", + " if status != 201:\n", + " logger.error('Failed to create MEC service')\n", + " else:\n", + " logger.info('mec_service_resource: %s', mec_service_resource)\n", + "\n", + " # Now, we can send a new request for services for our MEC application\n", + " # Get the list of the MEC sevices for our AppInstanceId using CAPIF endpoints\n", + " result, status = capif_get_mec_services(sandbox_name, app_inst_id.id)\n", + " if status != 200:\n", + " logger.error('Failed to get the list of MEC services for our AppInstanceId using CAPIF endpoint')\n", + " else:\n", + " logger.info('capif_get_mec_services: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('serviceAPIDescriptions: ' + str(data))\n", + " # b'{\"serviceAPIDescriptions\":[{\"apiName\":\"demo6 MEC Service\",\"apiId\":\"6172de7d-ed42-4ba2-947a-a0db7cc8915e\",\"aefProfiles\":[{\"aefId\":\"3daa9b79-ba81-4a74-9668-df710dc68020\",\"versions\":[\"1.0.0\"],\"interfaceDescriptions\":{\"uris\":null,\"fqdn\":null,\"addresses\":null,\"alternative\":null},\"vendorSpecific-urn:etsi:mec:capifext:transport-info\":{\"name\":\"HTTP REST API\",\"type\":\"REST_HTTP\",\"protocol\":\"HTTP\",\"version\":\"2.0\",\"security\":{}}}],\"vendorSpecific-urn:etsi:mec:capifext:service-info\":{\"serializer\":\"JSON\",\"state\":\"ACTIVE\",\"scopeOfLocality\":\"MEC_HOST\",\"consumedLocalOnly\":true,\"isLocal\":true,\"category\":{\"href\":\"http://mec-platform2.etsi.org:31111/sandbox/v1/statistic/v1/quantity\",\"id\":\"98a5039d-2b7c-49b1-b3b1-45a6ebad2ea4\",\"name\":\"Demo\",\"version\":\"1.0.0\"}}}]}'\n", + " logger.info('apiId: ' + str(data['serviceAPIDescriptions'][0]['apiId'] + ' matching MEC serInstanceID'))\n", + " logger.info('aefId: ' + str(data['serviceAPIDescriptions'][0]['aefProfiles'][0]['aefId'] + ' matching MEC TransportInfo.Id\"'))\n", + "\n", + " # Delete the MEC servce\n", + " delete_mec_service(mec_service_resource)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Annexes\n", + "\n", + "## Annex A: How to use an existing MEC sandbox instance\n", + "\n", + "This case is used when the MEC Sandbox API is not used. The procedure is the following:\n", + "- Log to the MEC Sandbox using a WEB browser\n", + "- Select a network scenario\n", + "- Create a new application instance\n", + "\n", + "When it is done, the newly created application instance is used by your application when required. This application instance is usually passed to your application in the command line or using a configuration file\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bibliography\n", + "\n", + "1. ETSI GS MEC 002 (V2.2.1) (01-2022): \"Multi-access Edge Computing (MEC); Phase 2: Use Cases and Requirements\".\n", + "2. ETSI GS MEC 010-1 (V1.1.1) (10-2017): \"Mobile Edge Computing (MEC); Mobile Edge Management; Part 1: System, host and platform management\".\n", + "3. ETSI GS MEC 010-2 (V2.2.1) (02-2022): \"Multi-access Edge Computing (MEC); MEC Management; Part 2: Application lifecycle, rules and requirements management\".\n", + "4. ETSI GS MEC 011 (V3.2.1) (09-2022): \"Multi-access Edge Computing (MEC); Edge Platform Application Enablement\".\n", + "5. ETSI GS MEC 012 (V2.2.1) (02-2022): \"Multi-access Edge Computing (MEC); Radio Network Information API\".\n", + "6. ETSI GS MEC 013 (V2.2.1) (01-2022): \"Multi-access Edge Computing (MEC); Location API\".\n", + "7. ETSI GS MEC 014 (V2.1.1) (03-2021): \"Multi-access Edge Computing (MEC); UE Identity API\".\n", + "8. ETSI GS MEC 015 (V2.1.1) (06-2020): \"Multi-Access Edge Computing (MEC); Traffic Management APIs\".\n", + "9. ETSI GS MEC 016 (V2.2.1) (04-2020): \"Multi-access Edge Computing (MEC); Device application interface\".\n", + "10. ETSI GS MEC 021 (V2.2.1) (02-2022): \"Multi-access Edge Computing (MEC); Application Mobility Service API\".\n", + "11. ETSI GS MEC 028 (V2.3.1) (07-2022): \"Multi-access Edge Computing (MEC); WLAN Access Information API\".\n", + "12. ETSI GS MEC 029 (V2.2.1) (01-2022): \"Multi-access Edge Computing (MEC); Fixed Access Information API\".\n", + "13. ETSI GS MEC 030 (V3.2.1) (05-2022): \"Multi-access Edge Computing (MEC); V2X Information Service API\".\n", + "14. ETSI GR MEC-DEC 025 (V2.1.1) (06-2019): \"Multi-access Edge Computing (MEC); MEC Testing Framework\".\n", + "15. ETSI GR MEC 001 (V3.1.1) (01-2022): \"Multi-access Edge Computing (MEC); Terminology\".\n", + "16. ETSI GR MEC 003 (V3.1.1): Multi-access Edge Computing (MEC);\n", + "17. 3GPP TS 29.222: 3rd Generation Partnership Project; Technical Specification Group Core Network and Terminals; Common API Framework for 3GPP Northbound APIs\n", + "Framework and Reference Architecture\n", + "18. [The Wiki MEC web site](https://www.etsi.org/technologies/multi-access-edge-computing)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb b/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb index 2448f8d6b08e0075fcd05279dc0105aec9854def..d20e87f4c913ec30d3219d8939e3abc5799324eb 100644 --- a/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb +++ b/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb @@ -10,7 +10,7 @@ "\n", "## Introduction\n", "\n", - "3GPP CAPIF (Common API Framework) is a standardized API management framework designed to enable a unified northbound API approach across 3GPP network functions (see 3GPP TS 23.222 version 18.6.0 Release 18/ETSI TS 123 222 V18.6.0 (2024-06) and 3GPP TS 29.222 version 18.6.0 Release 18/ETSI TS 129 222 V18.6.0 (2022-06)).\n", + "3GPP CAPIF (Common API Framework) is a standardized API management framework designed to enable a unified northbound API approach across 3GPP network functions (see 3GPP TS 23.222 version 18.6.0 Release 18/ETSI TS 123 222 V18.8.0 (2025-07) and 3GPP TS 29.222 version 18.6.0 Release 18/ETSI TS 129 222 V18.7.0 (2025-01)).\n", "\n", "This tutorial introduces the step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile as described in ETSI GS MEC 011 (V3.2.1) Clause 9. The cartoon below illustrate the \n", "\n", @@ -118,19 +118,31 @@ }, "outputs": [], "source": [ - "REGISTER_HOSTNAME = 'lab-oai.etsi.org' # capif-prev.mobilesandbox.cloud\n", - "REGISTER_PORT = 31120 # 31120\n", - "REGISTER_USER = 'admin' # Basic AUTH for registration\n", - "REGISTER_PASSWORD = 'password123' # Basic AUTH for registration\n", + "USE_OCF_SANDBOX = False # Set tot True if using OCF sandbox instead of lab-oai.etsi.org\n", + "\n", + "if not USE_OCF_SANDBOX:\n", + " REGISTER_HOSTNAME = 'lab-oai.etsi.org' # OCF register server \n", + " REGISTER_PORT = 31120 # 31120\n", + " REGISTER_USER = 'admin' # Basic AUTH for registration\n", + " REGISTER_PASSWORD = 'password123' # Basic AUTH for registration\n", + " CAPIF_HOSTNAME = 'lab-oai.etsi.org'\n", + " USER_PASSWORD = 'password123'\n", + "else:\n", + " REGISTER_HOSTNAME = 'register-opencapif.etsi.org' # capif-prev.mobilesandbox.cloud\n", + " REGISTER_PORT = 443 # 31120\n", + " REGISTER_USER = 'yann' # Basic AUTH for registration\n", + " REGISTER_PASSWORD = 'yannpass123' # Basic AUTH for registration\n", + " CAPIF_HOSTNAME = 'opencapif.etsi.org' # OCF Server\n", + " USER_PASSWORD = 'yannpass123'\n", "\n", - "CAPIF_HOSTNAME = 'lab-oai.etsi.org' # capif-prev.mobilesandbox.cloud\n", "CAPIF_PORT = 443\n", "\n", - "USER_PASSWORD = 'password123'\n", - "\n", - "TRY_MEC_URL = 'try-mec.etsi.org' # MEC Sandbox URL\n", - "TRY_MEC_SESSION_ID = 'sbxgs9x587' # MEC Sandbox identifier\n", - "TRY_MEC_PLTF = 'mep1' # MEC Platform identifier (depending of the network scenario loaded)\n" + "TRY_MEC_URL = 'try-mec.etsi.org' # MEC Sandbox URL\n", + "#TRY_MEC_URL = 'mec-platform.etsi.org'\n", + "TRY_MEC_PORT = 443\n", + "TRY_MEC_SESSION_ID = 'sbxgs9x587' # MEC Sandbox identifier\n", + "#TRY_MEC_SESSION_ID = 'sbxykqjr17'\n", + "TRY_MEC_PLTF = 'mep1' # MEC Platform identifier (depending of the network scenario loaded)\n" ] }, { @@ -230,7 +242,7 @@ "source": [ "#### Login\n", "\n", - "The login operation is required by ETSI TS 123 222 V18.6.0 (2024-06) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.6.0 (2022-06)." + "The login operation is required by ETSI TS 123 222 V18.8.0 (2025-07) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.8.0 (2025-07)." ] }, { @@ -250,6 +262,10 @@ "\n", " logger.debug('>>> process_login')\n", "\n", + " if USE_OCF_SANDBOX:\n", + " logger.debug('process_login: Not applicable')\n", + " return '', ''\n", + "\n", " try:\n", " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/login'\n", " logger.debug('process_login: url=' + url)\n", @@ -280,7 +296,7 @@ "source": [ "### Logout\n", "\n", - "The logout operation is required by ETSI TS 123 222 V18.6.0 (2024-06) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.6.0 (2022-06)." + "The logout operation is required by ETSI TS 123 222 V18.8.0 (2025-07) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.8.0 (2025-07)." ] }, { @@ -339,7 +355,10 @@ "\n", " # Login\n", " refresh_token, admin_token = process_login()\n", + " if USE_OCF_SANDBOX:\n", + " return\n", " if refresh_token is None:\n", + " logger.error(\"Login unsuccessful\")\n", " return\n", "\n", " # Print obtained tokens\n", @@ -368,7 +387,7 @@ "\n", "The next step is to create a new user associated to our CAPIF application to obtain a user UUID. It will be used to genereate ceertificates to be used during TLS mutual authentication and API onboarding and offboarding for instance.\n", "\n", - "**Note:** It is required by ETSI TS 123 222 V18.6.0 (2024-06) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.6.0 (2022-06).\n" + "**Note:** It is required by ETSI TS 123 222 V18.8.0 (2025-07) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.8.0 (2025-07).\n" ] }, { @@ -401,6 +420,10 @@ "\n", " logger.debug('>>> create_user')\n", "\n", + " if USE_OCF_SANDBOX:\n", + " logger.debug('create_user is not applicable against OCF Sandbox')\n", + " return ('', '')\n", + "\n", " try:\n", " user_name = str(uuid.uuid1())\n", " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/createUser'\n", @@ -462,6 +485,10 @@ "\n", " logger.debug('>>> delete_user')\n", "\n", + " if USE_OCF_SANDBOX:\n", + " logger.debug('delete_user is not applicable against OCF Sandbox')\n", + " return 0\n", + "\n", " try:\n", " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/deleteUser/' + p_user_uuid\n", " logger.debug('delete_user: url=' + url)\n", @@ -522,7 +549,7 @@ "\n", " # Login\n", " refresh_token, admin_token = process_login()\n", - " if refresh_token is None:\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", " return\n", "\n", " # Print obtained tokens\n", @@ -530,6 +557,8 @@ "\n", " # Create a new user\n", " user_name, user_uuid = create_user(admin_token)\n", + " if USE_OCF_SANDBOX:\n", + " return\n", " if len(user_uuid) == 0:\n", " return\n", "\n", @@ -668,15 +697,21 @@ "\n", " # Login\n", " refresh_token, admin_token = process_login()\n", - " if refresh_token is None:\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", " return\n", "\n", - " # Create a new user\n", - " user_name, user_uuid = create_user(admin_token)\n", - " if len(user_uuid) == 0:\n", - " return\n", - "\n", - " auth = get_auth(user_name, USER_PASSWORD)\n", + " # Get auth & URLs\n", + " auth = None\n", + " user_name = \"\"\n", + " user_uuid = \"\"\n", + " if USE_OCF_SANDBOX:\n", + " auth = get_auth(REGISTER_USER, REGISTER_PASSWORD)\n", + " else:\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", " if len(auth) == 0:\n", " return\n", "\n", @@ -693,7 +728,8 @@ " time.sleep(5) # Sleep for 5 seconds\n", "\n", " # Delete the newly created user\n", - " delete_user(user_uuid, admin_token)\n", + " if not USE_OCF_SANDBOX:\n", + " delete_user(user_uuid, admin_token)\n", "\n", " # Logout\n", " process_logout()\n", @@ -726,7 +762,7 @@ "\n", "Fo the process of onboarding and offboarding APIs, the TLS mutual authentication is required. We already got the peer certificate to verify peer but we need to generate our own certificate to be verified by the CAPIF server. This is the purpose of the following functions to generate the public/private keys and generate a CSR and request certificates for each of the three functions AMF, AEF and APF.\n", "\n", - "**Refer to:** ETSI TS 129 222 V18.6.0 (2022-06) Clauses 5.11 CAPIF_API_Provider_Management and 8.9 CAPIF_API_Provider_Management_API\n" + "**Refer to:** ETSI TS 129 222 V18.8.0 (2025-07) Clauses 5.11 CAPIF_API_Provider_Management and 8.9 CAPIF_API_Provider_Management_API\n" ] }, { @@ -998,15 +1034,21 @@ "\n", " # Login\n", " refresh_token, admin_token = process_login()\n", - " if refresh_token is None:\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", " return\n", "\n", - " # Create a new user\n", - " user_name, user_uuid = create_user(admin_token)\n", - " if len(user_uuid) == 0:\n", - " return\n", - "\n", - " auth = get_auth(user_name, USER_PASSWORD)\n", + " # Get auth & URLs\n", + " auth = None\n", + " user_name = \"\"\n", + " user_uuid = \"\"\n", + " if USE_OCF_SANDBOX:\n", + " auth = get_auth(REGISTER_USER, REGISTER_PASSWORD)\n", + " else:\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", " if len(auth) == 0:\n", " return\n", "\n", @@ -1068,7 +1110,7 @@ "The purpose is to export the MEC Profile for CAPIF API into our CAPIF application. To achieve it, we need to fulfill the following requirements:\n", "1. Create an instance of a MEC Sandbox using the '4g-5g-macri-v2x' network scenario\n", "2. Set TRY_MEC_URL, TRY_MEC_SESSION_ID, TRY_MEC_PLTF constants accordingly\n", - "3. Build the ServiceAPIDescription as described in ETSI TS 129 222 V18.6.0 (2022-06) Table 8.2.4.2.2-1: Definition of type ServiceAPIDescription. This is the role of the function below" + "3. Build the ServiceAPIDescription as described in ETSI TS 129 222 V18.8.0 (2025-07) Table 8.2.4.2.2-1: Definition of type ServiceAPIDescription. This is the role of the function below" ] }, { @@ -1103,7 +1145,7 @@ " \"versions\": [\n", " {\n", " \"apiVersion\": \"v1\",\n", - " \"expiry\": \"2025-11-30T10:32:02.004Z\",\n", + " \"expiry\": \"2028-11-30T10:32:02.004Z\",\n", " \"resources\": [\n", " {\n", " \"resourceName\": \"MEC Profile of CAPIF\",\n", @@ -1134,13 +1176,14 @@ " \"interfaceDescriptions\": [\n", " {\n", " \"ipv4Addr\": TRY_MEC_URL,\n", + " \"port\": TRY_MEC_PORT,\n", " \"securityMethods\": [\"OAUTH\"]\n", " }\n", " ]\n", " }\n", " ],\n", " \"description\": \"MEC Profile of CAPIF\",\n", - " \"supportedFeatures\": \"fffff\",\n", + " \"supportedFeatures\": \"020\",\n", " \"shareableInfo\": {\n", " \"isShareable\": True,\n", " \"capifProvDoms\": [\n", @@ -1178,7 +1221,7 @@ "To proceed, we need to enable the TLS mutual authentication using the security material obtained during the onboarding APIs operation ([Onboarding APIs](#onboarding_apis)), i.e. the AEF certificate and the AEF private key ([Generate certificates](#Generate_certificates)).\n", "\n", "\n", - "**Refer to:** ETSI TS 129 222 V18.6.0 (2022-06) Clauses 5.3 CAPIF_Publish_Service_API and 8.2 CAPIF_Publish_Service_API\n", + "**Refer to:** ETSI TS 129 222 V18.8.0 (2025-07) Clauses 5.3 CAPIF_Publish_Service_API and 8.2 CAPIF_Publish_Service_API\n", "\n", "Before to proceed with the steps above, let's create 2 helper functions to simpily the implemantation of the CAPIF publish API. These helper functions cover the following operations:\n", "- Onboarding operations\n", @@ -1221,15 +1264,21 @@ "\n", " # Login\n", " refresh_token, admin_token = process_login()\n", - " if refresh_token is None:\n", - " return dict()\n", - "\n", - " # Create a new user\n", - " user_name, user_uuid = create_user(admin_token)\n", - " if len(user_uuid) == 0:\n", - " return dict()\n", + " if not USE_OCF_SANDBOX and refresh_token is None:\n", + " return\n", "\n", - " auth = get_auth(user_name, USER_PASSWORD)\n", + " # Get auth & URLs\n", + " auth = None\n", + " user_name = \"\"\n", + " user_uuid = \"\"\n", + " if USE_OCF_SANDBOX:\n", + " auth = get_auth(REGISTER_USER, REGISTER_PASSWORD)\n", + " else:\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", " if len(auth) == 0:\n", " return dict()\n", "\n", diff --git a/examples/demo6/python/notebook/ETSIMEC_TechTalk_tutorial.ipynb b/examples/demo6/python/notebook/ETSIMEC_TechTalk_tutorial.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..e61a8b949502cc6efc7fe4965bbee8c329d14ea1 --- /dev/null +++ b/examples/demo6/python/notebook/ETSIMEC_TechTalk_tutorial.ipynb @@ -0,0 +1,820 @@ +{ + "cells": [ + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7MAAACUCAYAAABBX358AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAIajSURBVHhe7Z13nFxV+f/ft03fvpvdTe+9EUISQkIHqSIgVUEQ8QsqqCj2wk/F3hVUpKggIL33ThLSQ3rv2d53p9/2++POltmSAslO2fN+va5l7pnJztmzM+dznuf5PJJt2zYCgUAwQIgZUBuGVH/ySRIUeCDg6n4nPWiLQWsMzBTP0+EiS5Djgly3M7epJqJDWxyiRvc72UfA5Vwupfud/kE3IRh35jvdkSQIaBBwgyZ3vysQCASCI0USYlYgEAw0Iga0RiFmdr/T/+S6IccNShoIsO6EdGiJgmF1v5O++BOCNh2EQtx0DgTCevc72YdHddaxV+1+p/9oi0FzFDJhU6PKUOh15k0gEAgEHx0hZgUCwYAkZjhCI5IGkTOf5ggBd4oiWwcjokNr3JmvTMGjOlFar9b9Tv9j29AUdSKHA4FCb2qzDcK6I2rT4aDqULRnEwTS9DBLIBAIMgEhZgUCwYAlnSJnLsURtP40EGDdiSaEfyalzKqyIxRy3N3v9D+27cxfWxysAfCNm+psA91yBG2mHCD4NOcAQERpBQKB4MgRYlYgEAxoDMsRGumw8ZUlRwTkutKj7rMruulEaENpME+HiyQ5IiHH5YjbVBOMOyJLz6C07Y+KPzHvqaqjtRNpx21xMDNgvtPp8EUgEAgyCSFmBQLBgMdKRM5aY93vpIZ2AaalSAj0hZGIeGWC0U5XfJozn+40iHwNJGOodEj3zqS0Y9r/9tOk5lsgEAgyASFmBQKBIEG7oE2HVFCP6qRrplvqoY1jCpUuwv9wSac07nRKbz/WqLKzjlNZR2vZ0BBOj/r4w0GVocibHocvAoFAkO4IMSsQCARdCMYdoZEODr6akjCISaEQ6Iu2mGNslGnkuiHf0/3R/sewnAhtW4YdCnwUpMS853qc/50KTLszqyATdj2y1HkIIKdq0gQCgSADEGJWIBAIuhHWHUEbT4PURKlL/9R029S29/bU02CejoSAC/I8qTMoaqe9rjNdsgGONYHEOk5l/XJb4rAqE+poSbMUeYFAIEhHhJgVCASCXogkHHzTpSWNP9G+J1WGOn0RM6A+7ES+Mgm3CsVeUFIorNqJ6FAfyYyI4cdFU6DEl1pBG07ULafL3/ahcCnOQUA6ZmgIBAJBqhFiViAQCPrAtKA2lD7us4rsCIF0E7TpJvwPl3SqS06nbIBjjUtx5t2XwvrlmOlExTOlbrk9QyOQJs7cAoFAkC4IMSsQCAQHId3MetK1hUemClotIazSwRgqE/v5flSURE1oKtexbUNjNLPaTWmKk1GQbk7nAoFAkCqEmBUIBIJDoJvQkkaCVkoIgVx36gx1eiNmOL1oI2kyT4dLOgirdkzLSdvOlFYyH5eACwq93R/tPzKxblmTIeB2DrUEAoFgoCPErEAgEBwG6SZoSRNDne5kqqCl3XE3DYy2dNOZw0yKGH4c0qG3aiiR5p0pZmbtacc57tQbmQkEAkEqEWJWIBAIDhPDcgRtOokMn+YIsHSqo9VNaI5lpqBNB2FFopVMa2xgtO4B8KqOw3Qq13EmpnkLt2OBQDDQEWJWIBAIjoB0FLTuRN2nNw3qPtvRE7XGoQwUtN6EMVSqBYKV6I3aGnPSYbMdt+II2lQaclk2NISdGvBMQZGgyJfaeRMIBIJUIcSsQCAQHCGG5QiMYBoJ2nQyMmonkwVtOjjuttMWc9KOM6U36sdBkx1Bm8p5N63OfrSZgiJ1lh1IIu1YIBAMIISYFQgEgo+AmYjQppOglbsYQ6UT9eH0qjU+XCQJirypFVbtBBPiyhgAglaRIc+d2r6qtu0I2pZYZvX/9ScEbarT5AUCgaC/EGJWIBAIPiLpKGhJGBnluBxRkA6YiUh2W5rN0+EgS06kMB2cYwdSL9p0OZjJxKi4R3Xqvr0i7VggEAwAhJgVCASCj4FlO4I23Yx6/AljqHTpR2nZ0BLNTEErJQ4I8jzd7/Q/Ed0RV5nWz/ejkg4O06G4M+eZ4nRMoh91boqj2wKBQNAfCDErEAgER4F0TKXVZCjxp1frnuZoZtUidiXHDQVpIGhNC2pDoGdQtPDj4NOcdO9U1oJGdOfQKtOi4u2CNp0+AwQCgeBoIsSsQCAQHCXSUdAqEhT7Uu/M246dELTpFsk+XNKlt2/cdCLdmeS6+3FIhxZUmXqI4Facz4B0KTsQCASCo4kQswKBQHCUMC2nv2o6te0hkXKY50kfp+N0Tc0+XHyaM5+pNtmJJ9yi0+0A5VjhUR1jqFQezOiWc4iQaXPuTrSbEnW0AoEg2xBiViAQCI4i6brZVRJGRulSQ5fpgtarOvOZykghCdfd2vDAqaFVJBjkT20tuJmo/04347dDoSkQ0Jx0eYFAIMgWhJgVCASCo0y6Ctp0cuYlg0VBO55EtMuT4mhXJvfz/Si4FGcdpzLKaNnOnGda/Xe7mVmu6EcrEAiyBCFmBQKB4BgQN53a0GiaRcw6NrMe53+nGtN25indUrMPF7eSSN9McQq3bkFrdGAJ2lx36nsAt8ac9Ztp+F1Q6BGCViAQZD5CzAoEAsExImY4G91YGjqg5rid+sNUtjxpx7ShOZK5QixdapKNRN/jTD0YOFI0xVnDqRa0wbjzd25l2G7Kl0g5dqcwZVsgEAg+LkLMCgQCwTEkajiptOkoaAMuR4QpaSBoMz1VVpEdYZXqmuRMT90+UtLlICEYd+bdzLAdVbpkFggEAsFHRYhZgUAgOMZEEhFaPQ0Frd/liLBUt5ohS8yM8j2OOEglViJ1e0AJWrezllNJSHcErZFhrXtUubMfrUAgEGQaQswKMg4bqAvp7G+JcaA1hmHZ5HlUSnwaZQGNYp+GIktEDYuqtjgNYYOYYeHRZAIuBb+mUOhT8XXpq2FYNo0Rg9aYQdSwUWUJrybj0xS8LhXLsrAMk/qWEDurWqhtDmHZNnl+N6X5foYUBRhekpP0cx4OcdNiZ2OUitY4zVGDkG5i25DnURma62JEnodiv4qcKGyybOe9t8ZMgrpJMGYiS1Ds0xiS6yLgUrCBvU1RdjfHqAnptEUN3KrMiDw340t8lAU0TMtmd3OMytYYTRETSYJSv8bIAg+Duvx7gqNHWHcERjpudNOl1QyJCG1zBvdPlaVOg51Ukulu0UdKukTG0/nv/GBIEuQmeiiLj3+BQJBJCDErSHtM2xFnKyuDrKkKsa85Sq5HYXqpnzlDc5hU7MXnUtBNmx2NEdZUBdnfEsetSgzPczO+yMvQPDd5HrXD8Ma0bPa3xNlaH6YurKNKEuW5LkYVeinPcaFJEI7pbNnfyOJNFazdXU8wqjO4MMCssYOYMaqEkYNyCXg1pKP4zW9Y0BTR2dcSY2tDhD2NUUK6RZFPZdogH9PL/JR22a3FDIvqoM72xig7GiJEdIvBOS5mDwkwutCDBATjJh9Wh3h/byvrqkKEdIsJxR5OG5XP8YMDFHpVGiIGG2rCrK0OUhvSKQ24mFXuZ3qZn8JUWoZmGcG4k0qbjhtdr+ZsZlPZw7OduOlEuDJV0EpAvjf1rtGZ6rj7UVEkx9gs1fMe1p05j6dhJsahCCQEbTpkaggEAsHhIMSsIO2wbJvKNp0VFW28vqOZpQdaaYuZTCj2ce64fM4bX8ioAg8ADWGDd/e28PauZipa4wzNdbFwRB5zh+YwNM+dZG4TjJmsrgrxwf5WakI65QEXx5X7mTUkh0KP44Cxr7aVN9fu46UVe9i4r4Ecr8aCKUM45/iRzB5XSkHA+Xf7k5aYwebaMCsrQ2yuCyNJMGWQj4XD85hY4kVNvEnbtqkLGyw70MZ7e1qoCeqMLfJy9ph8Zg8JoMoSpmWzvjbMM5saeGlbI5VBndEFbs4ZV8g54/KZWurHMG3WVAV5c2cLm+vD5LgUThyWw8kj8xhV4EkLw6BMJhSHhkj3R9MDTYZiX2p7eLaT6YJWlpxIYTr09GyODhxBS5qkeusm1IYyr4aWRB1tsc+JdgsEAkG6I8SsIC2IGhYbayO8tauJN3a2sLoqRMyC6SVurpw+iIsmFjIsz9mdHGiN88KWBl7Y2sju5hjjirxcPLmIs8fkU97tSL4pYvDB/lZe2NrEzsYoY4s8nDu+gFNG5pPjkrFtm837Gnlm6Q6eXbaTDXsaKCvwceHc0VyxcCKzxg7CnQ47+wSWDbubory1q5nF+1oJ6xYzyvycP76QaWX+JCOfitY4L21r5LmtjTRHDBaOzOPiSYXMGpyDIoFuWqyoCPLfdXU8t6WJFlNmiNfmrDEFXDypkBOH56LKEssPBHlmSwMrK4LkuhVOH5XPWWPzGF/sSwvjoEykJeqkgKYj6WQIEzOceUq39kaHi5QQtKkWVnZC0A6UlON0mfeI4RwiZGINeLr0UBYIBIJDIcSsIGVEDYtVlUFe2tbEW7ua2d5iYWseiohw0cQCrjluENNL/QBUtcV5aXsTT21qYEVFkGKfxmVTirhqWgkTS7xJrxvRTZYeCPLYhjre39tGsU/l01OKuHRKCeUBZ4e+u7qFp5bs4JF3t7BqZx0+t8oFJ4zi+rOmcMq0oXhd6f8NbtuwqynKi9saeX1nM7YNZ43J56JJRYzM79zFmZbNysogD6yu4fWdLZT4NS6fWsylk4sYkRjXGDF4fmsj/1pdw7omG5Aoc+mcNSafy6YWM3doDqYFr+1o4tH1dWyqizAsz80nJxZy1pg8RuT3f8Q602lKY3HhUpwa2nTIMI8k3KAzMWWTNEo5thOmUG0DxBRKSvRTzkvxR1M0IWgz8UDGlWh9lA4HWwKBQNAXQswK+hXTstlYF+a5LY28sr2J7U06uP3YepQZRSqfmzmICycWkudRiRoWb+1q4dH1dby7N0gEjWkFEjfOLuWCCYUEXMkR0231EZ7Y1MATG+upbNM5ZWQu/ze7jFNG5qHITg3sa6v38sDrG3ntw/1ELYWxJW6+eM50rjplIkOLA0mvl0noFizb38p/19WxoiLIpGIvn505iNNG5eHqEj490BrngdU1PLS2jta4xemjcvnczBJOGZmHW5UxLJvF+1q5b1UNb+4NYag+7FiIsfkqF08q4oqpxQzPd7OvJcaj6+p5bmsTbVGDuUNz+NTkQk4ankOeOMo/LEwLmtO4J6iWaHmS6h6eZLCpTjvpYgoFUB925nOgkA4px7GEoM3ElPl0aX0kEAgEfSHErKBfaIoavLK9mcc31LGqKowuu5FkGcWIcvJwPzfOLmPBiFwUSWJfS4z/ra/nqU317Iuo2JbJgnIXN88dzMKRuahdUlujhsW7e1p4aG0d7+4LYwMXjc/lptllTC31AVDZGOLfb2zkgdc3sL02CrbFSROK+dpFx3PeCaPwpYPjzVFkX0uMhz6s4/GN9fg0mWtmDOLSKUWUdNmNNIR1Hlxbx31r6mg0XIz1G3xmRgmXTC6m2OfMx9rqEP9YUc1LO1rQFS+2ZeK145w8MpfrZpZw8shcYqbNS9uaeWhtLQeiKgWyzrnj8rhgYiFjC1McEskAdNOJ0KZr1CadIjPBuCNorQz9xkqX1FfDcoTVQGnbQ5oI2rjppMxHMvAgIV2MtQQCgaA3hJgVHFN2NkZ5dH0dz2xupCbqqFBJklFsnVNHBLh5ThknDHFa2qypCvHAGicVNiz7sI04J5a5uGVeOfOH5yYZDzVHDZ7b0shDH9axtU1CsU0uHOPnS3PLmVDspB1vq2jirhc+5L/vbKEhIiFZBmfPKOcbl8zmtOnDULPc3aIxYvDgh3Xcv7oGw7K4enoJ18wcxPBE7TFAfVjngdW1/GttA0HJS7EU5vKpRVw7c1BH/fH62jB/X17FKztaMWQN27aRLJPJRRo3HF/GJycWoskS7+1p4d9r6tgV0ZCNKCeUebh4ciHHD/ajZflcfxxiJjRF0jeNNp1SjttijvjPVCScuUy1sDIsR1ila1bA0UaSID8NzLhMyzGF0jM0wyAvDdK2BQKBoDtCzAqOCWurQ/xrTS2v7WwhYmtg6iArKFgsHObjy3PLOX6wk9a7vCLIP1ZU8f6+EKbiBttmfK7NV08czJlj8jvcegGaIwb/21DPfz6spVp3Y5s6pwxx8fWThnTU1+6obObXT67g4Xe2EDJVsAwWThzE966Yy9nHjUAeYHa8TRGDf6+p5b419Ri2zWWTC7hhVmlHvSzA3uYYf1layXM7gpiKm3wiXD61iGtmDGJwriNqV1UG+dMHVSw6EHZ2h7azSxwZgBuOL+OSyUV4NZlFe9t4ZG0dO8MqtmkwLk/iksmFzB+eg0f0e+iVsO7UhabrJjedBG1LzJmrTCVdUo51K9HPNwMjhR8FWXLWcKqji5nu0t2+dgfY16hAIEhjhJgVHFXW14S4Z0U1b+4JYkgqthFHkmSQFaYVqXxt/mAWDM9BkiTWVIW4e1kV7+8PYskaSDJ5is7/HT+Iq2cMwqd1Cp+2mMnjG+v51+paauIqyDJj/Ca3zR/CGWPykIDa5gi/fWoF97yyvmOzO6Hcz4+umsdlC8ahqenjSpwKKlrj/OmDSp7ZHiKgwVVT8rnuuEFJ6ccf7A/yq/cPsLnF0aoFcpzPHTeIz84oIdetYFo2r+1s5g9LKtkblLCMGJKkgKIwKgA3zSnjwgmFyBK8t7uVR9c3UGt4sEydIR6TiycXsXBkDm4hansQjDtCzUxnQZsGKce27cxTJreaSZeUY91MCNoMFVZHipIQtF1adaeEeGLe07W84FCIXrQCgSCdEGJWcFTY3xLnb8ureGlHK3EUbCMONsiam2K3xU2zS7l0ShEuRWJvc5S7lzljTUkFywJszhoV4JsLhjIsr3OnYVo2L29v5u5lVewJgSQreOw4104v4objSwm4FWK6yb/f2MhPH13GgaYYIBFwwdcumsk3LplNvj/FO8Y0Y9mBNn7xXiU7Ii4KCfOFWcVcPrUET+LwIKJb3L+6hvvW1BNDw7YthnltvjKvnPMTQrUtZvLPldU8uK6RmC07hxaKCpLE9GKNr88fwtyhASK6xcvbmnh+awsxxYtp6Az3WVwytZg5QwOitU83WmPOJjddSacIbV0486OK6VLLmcnC6khRZecgwZ9iQRtNuHTH0rS84FD4NSdtu5sPo0AgEPQ7GS1mLdsmehQLzVyqjCSBIstYx3BabNtGkXs/0rRtqG0Jsa2iib01bTS0RWgKxlAVicIcL8OKA0wcVsio0ry0qPkMxU0eWlvHg2sbaDUVbD0Gto2kqsjABeNyuPXEwQzya0QNm4fW1vLAmjraTAVbjyNpLgo1i2+cVM4FEwrpqm021ob545IqllVFQZKRJImphTLfOXkI0xIpxat21PKNe9/h3Y3VzpMkiZMnlfKn/zuNmaNLuryaoCth3eKeFdU8uKEFU9aYkGNy2/zBzB3m1C8DrKsJ8dO3D7Ctxca2TMBm/mAvty0Ywvgip3BqXXWYX7x3gE1NFlb7715zoWBx/thcbplXRmnARU1Q57H19aysjiOrGqahM7FQ5bKpRYxLvJbAoTma3lFHd0LQptq02kikyWayM68sOYI21ZHCTO/ne6SosjPvqXbqznRB69Ug1wVZ5qEoEAgyjIwWs/99ews/fHAxbu3oHA3KMnzhE9P47GmTufTOZwnHDWTp6IaOonGTm8+bwc3nz0h6vLYlzBOLtvH0Bzv4cGcdDW1RbLmXbwjLwqtJzBo7iC+dP5PLFo7v01znN0+u4Lllu3qdH8O0GFuez9++fCbaR8wVWrq/jT8sqWR7q41t6GCZIEnImptyL9w2v5zTR+cBsKYyyG8WVbKlxXLGJkTP3DIX3z9lKEMTdZkkBPK/1tTyUCLqh22hyfCZqQV88YQyPKpMTDf53VMr+fnjKwjFbbBtfC6ZH1xxArddPLvX9yzoybL9bfzsvQqq4m4kPcwlE/P40pxy8jzO/AXjJn9aUslTW1uxbRsUFZ9s8oVZJVw9vRiXIhPWTe5dWcN/NzSjW4m1IElImodil8lX55Vx/oRCAFYcCPLEhiZaLRXbspFtk/nDvFwwsYD8xL8pSP/2KYoEg/yQ6j+zbIgqKpLThzbVrU+iCUEby+C5PBK0hKBNddp81HDW8FE8l+9X3KqTXZAO2RoCgWBgktFi9prfvcJD7+9yzIWOBrLGo988k1yfi/P+34tgH4NvF0nhme+dw0XzxgAQiur89YU1/Pm5D6lsdiJb2JZz9YUkgaSAJHHJ3BHc97VP9Eil1Q2LObc9yod7mxyR2R1F4/ITR/C/b5/X/c4hCesWf19exeObWzBtyREvOKcBkqxw1kgft500mCKfRty0eWBVDf9Z14huy1hGHElWUGSJz04r4IsnlOLqIsY31IT51fsVbG2xsXUnilfisfnuwsGcNCIXgB1Vzdz01zd4c32V895khQnlOdxzy5mcPHVox2sJDo+aYJw7361gWY2zix3qtfj2wsHMGdoZpX12cwN/XFpLyADbNJE0jRMGaXx74RCGJ4ykFu1t5VeLqqiNghVPhBYVBZA4d0yAr88fTIFXpSVq8NTGJtbW6o4zsqKSqxhcODGfE4Y6EfeBTibUMroURwykOkIbMxyH40wVA6RZpDCThdWRki514HETakLO138mIkkwyCcitAKBIDUod9xxxx3dH8wEwjGDHz+0hIbWsCNobCc695EvwKdJ/OK6Bfzv/W0s2VIFltEpLI/GBRT6VX56zUnk+lzsrGrm0794nvvf3E5bOJZ4H0796CFJvObmqhB7qxu5dP44pC5R5G0VTfzi8eXout7z57AtkCRuvXA6x48tTXrZQ7G9IcL3XtvH+5U6tmkg2RayJKFqLnyqxK1zSvjKvHL8LoWKtjg/eGMfr+yJOgLINlE1Fzkume8tLOPqGSUoCUtEG/jf+np+9l4VdRHAiKN5PMwo1vj1J0Z09Ix9ccVuLr3zBdbtawbTAEXjwtnDeeJ7FzJ1RHG3n1ZwOARcCmeMyScci7OlLkLQkHlzVwumZTGt1I8iS0ws8TGzzMuaqjARSwEjTk1M5q2dzZQHNEYVeBie7+aUkblsrw9TH1fAspCxUSSb3UGJRbubGF/kYUS+h+MG+8lzw/5mHUkCw4ZNdXFqWmMML3APeNdjRXauuJm+fVVN2/m9qXJqjWBU2REloaN0ppkKLNsRkl7V+b2nClV20sgzeS6PBNN2DkP8WmrdeRXZiRTrafz3fijiZuJ9pDhbQyAQDDwyVsyu31vPr59YgXW0Pvllhekji7jlk8fx44c+oLLxGByTygpzxpdy6yePY2dVM+fd8Qxr9jSDGT88AdsblsXG/U3Mn1jOmPL8jodfXrWHxxfv7BDRSUgSLhl+es1JlOY7IvFweGNnM3e8XUl1RAIjhizhCFmXhzKfxM/OGMZZY/ORJIkVB4J874397GmzwYgiSzaq5maQV+bOM4awYISTfgzQFjf55bsVPLYliGmaSLaF6nLziRFe/t8ZwyjyOce9f3hmFTf+9U2aQ/FERFbmlvOn8s9bzyY/kGIXlQxHkSVOHJaDS7b5sCqEjcS6OoNtdUGOHxLAq8mU5bg4cViA9dVB2kwF9DgxW+bdPUFsy2RGeYA8j8IZY/JoDEbZ3WYhYSMDkmUSNBXe3t1KgUdmfLGXYXluxhS6ONAcx5I1JEun1dDYUBWkyKdS7B/Yx/ztIjGd041NKz1EmCI76box8yN/kqYcO9EuJ9WHA0ri348bmTuXR0K6zLumpP8B1sGwbOdnlyVhCiUQCPqXFH50fzyWbqnCQPnoIrA7kszCqUOpb4mweX9D7yLw4yLJnDZ9GAA3/OkNtlUFE0K2OxLIKiiac0kH+2awsSWFJxZvT3r0rbV7ndyf3pBkxg7OZ+zgTvF7MGwbHlxTyy8X1RA2bGQz1vHF7/Z4mVKk8qfzRjJrsJMi+sKWJn74diVNsfaxEi63h5G5Cr8+eygzy53+siTaxdz+yl7eq4gjmzEUCVyqxlWTc/j+qUPxaTKmZXP7fe9x232LiOsmYCErCj/7zDz+fNPpoj72KHLNzBJumTMIlyKh2Dqrag2+8fJedjQ4FrvD89z8+uzhTC5QcXu8KLaBhMVDG9v45bsHCOsWXlXm2ycP5dqpebg0FU2RUWWQrThx0+b3S+u4b1Utlm0zPN/NdccXMjpPIsfvRzHj6LbCM1vaeHdXK2Ym7uqOIl4t9W63h8K0oTGS+lrLgCv1PUQ/LjHDMf9KdZqvX3NMvgYKUcNZw6n+uPEl/t4z1eXdsJy664ES2RcIBOlBxorZdzcc6P5QMonepod9SRKnzxjGiu3VnR/EkpR8JXnt9kX7uF4uy+SMGcO5+8W1vLupunchK0m4XCpzxxXzuVPHc/XCsUwamue8n76wLdbtruv4v9G4yfKtNX0LcknmxEmD8boOHfkybZu7llXz4IZWsG0U20KVZVRZxu3xcUKpxi/OGsaQhIHTI+vq+fOKOkzLRrFNVFl2hGyOwi/OGsbYIm/Ha2+rj/Cd1/ezq9VGMmKoioJL07h2Rj43zylDliBumPzfX17nt8+u7WjhI8syv/38Ar5/5dwuP6ngaHHZ1CK+NLsEl6IgmXEqQjbfe2M/aypDAJT4NX525jAmFyi43F5UCWQzzrsVcX76dgVtMQtZgi/MLuWmWUVoqoqmqKiyjIKFbFv8b3Mbf/6gGsOyyfOoXD6tgCmFErkBPxommgRr62xe2NxCRE/xDjPF5LpTbw50KGJmeoiwHHfqnYE/Lu1GTHofH9/9RcDl1JMOFHTLEbRGGsx7QefXZMZhWNCQ5gZ2AoEgu8hIA6iWUIzjbv0vu2taexdsssrFc0fyhXOmEdMPb3clSXDe7FF86/73ueuFD3sVejHdIH6IbzpVUXqtvTEtmyFFAd74+aV88ifPsaWiuRdjJglZgn/ddg7XnD6p49GYbnLjn1/nwXd3OnW83ZEVpg3PZ+Ufr8KlKqzfU8/sr/2XuN5H/a2s8p+vn5X0b/SGadvctbSaV3ZHsYyEOVUCxeVhTqnG7QsH40v0J31kXT3/WdcEloVtOfMkqxrFXok7ThvC6MLOo/6t9VF++m4ljVEby4g7LsiyypVT8rj2OKeljm5Y3HTXG9z/5taEyZcEssLPr5nHdy+f0/FagmPDQx/W8fDGVse0S9Xwq3DbvEHMTxhxNYR17ni7kp0tptOWJ7EuJhbI/PDUIeQnnIGe3NjAfWsanJKAjr9Xx/X6E6O8fGVuKYosYdrw9o42drdJxKMRkMDl9lGg6Zw1LqfDYXkgEjehKZL+LTx8mmNklMp0TcNyDKEyvQdtOsylDTRHoK2Xc9dsJcflRKV7+x7vT9oSPad7+QbPCBTJmcdMP1wSCATpT0aK2aVbqjjp9kedQF1vH/WywpPfPY9L5o/rfueQVDWGCMccU5quuDWVh9/Zwrf+taR3QYmEqkg8+M1zmTayGL2b6LVtKMxxs3FfA+ff8VzvTsmSzOACLxv/fl0Pd+I3PtzLdb9/tdc2OnHDZNaYUp790UXIksQ/Xl7HTX97t3eXZ0nC51ZZ9afPMHGo0y6lN2zgnhW1vLgr7DjTdhWybg/Ti1W+d/JgvAkh+8SGBv69rhnbNDrGSoqC36Xww5PLmJIwcALYWh/h5+9V0RSzsXTnZ1Rcbi4c6+cLswd1jLvtn+/yh+fXdb4PxcVtn5zO775wcscYwbHDtuHPS6t5a18UMxZFUlU8isTtJw3qcDquaI3xo7cqqQtbWAlXa8XtZWqhzPdOGYw/UTz12PoGHlrfjNVlfSBJyC43nxjp4UtzypAk59Bn8Z4we4MJQQu4PF58ssGpo3wUDeA62rDuCFqzl4+8dCKQEAOpTJWMJcR/qiPFHxe/yxG0qZxL03bmciBF2nLdzrynmpaY04c2U5ETgjbT0/8FAkF6k5Fi9ndPr+KbDyzpQ6zJ5Ps0PvzrNYwY5ESQjhbfuPddfv9cF3HVFUlmdFkua/78WXJ9fX9yf/8/i/n5E2t6TzEGQOIrF87g+1fMpaygs02JZdkHjTLLstRRO/rZ377Cf9/b3vvPKSscN6qIZb+/uldh3M7jGxp4JBGV6ypkZZebsXkq3z+lnLxE5O2tnS38dUUdVpeILJKEoqjcMqeE00Z3/h4OtMb5yTuV1EcsLN2ZA8XtZf5gF187sazD3fh3T63imw8sTkSvbVA0Ljh+OE987wJRI9uPhOImP3mnku3NBlY8hqSq+DWF7ywo7XCY3lgb5s53q4noJnYi20Bxe5lTqnHbSeVoiZ34vStreHl3FDPmiFRICFrNzacnBLh6huNGbdrwzs4glWG5Q9BqLjcuyeSMMX5KAgNX0LbFHWGR7uS4Id/dd9l+fxDSnciWefBkmrQnkBC0qYwU6paz7jK5n++RIOGIsFTXq9s4YrY10eksE5ElZx5TPZcCgSB76VvNpDHvrj/Qa0AWnM3x6LI8NFWmuilEVePBr4MJxK6Yls17Gyr6djiWFU4YV3pQIQuws7K592hyBzZ/fXED07/8IFf/+iUefGsz2yubsAGvW+3zahd4wYjOim3VifrSXpBkTpw4+KBCdsneNp7Y1OzUvEpSR42sy+VmkEfiqyeWdgjZjTVhHviwEUWSUKBLPa2XiybkJQnZ1qjBn5fW0BKXkE3DGef2Mi5P5v9OGNQhZF9YvovvP7gkEb22QVYYW5rD379yhhCy/YzfpXDT7BJyVdBUDcWyiJtw17I6DrQ4O6wpg3x8bmYhmqqgKgqqLCPpMVbXGTz0YX3Ha33uuBLmlrlwe3wd60SVJGRT5+ktLby9qxUS6WknjfTjkXR0WSNiWLSGI7ToEi9vC9IQGiA76l7IcWXGprAtlvoNuF/LjohQMJ76udRkp352oLjU2ok1nGojIylDauYPhmVnviAXCATpTcZFZpuCUWZ85SH21wd7r5cFPC6VHK+Lg701y7bxuVXe+sVljBtc0P12D3ZWtXDcrQ/RFkmOVHagaNx908ncfN6M7neSuPrXL/HIop29R027IiUcjW0bnwZTRxRz9qwRXHziWGaOGYTcR8hjxfYaTrztkYQLbC8/p6zy8DfP5qpTJna/A0BVa5z/924VLVHLSQlNIEkymirzjfmDmFnuRIxboiY/eaeKyqDpRHATyJqLMfkq3zu5DF9CfFq2zV+W1rC82sBIROYkWSHglvnByeWMLHB26HtqWjn1u0+wt64tEZWVUFWFp753ARfOHd3xbwj6l5e3tfDg+hbMjtpYN2PzFb6zsLyjZvquZbUsqYxhxJy8OEmSkGSZm2YXc8oo51CjMWLw03cqqQ51piWDk5IecCl8b2FpR211fcjg8Y2thOOda1Fze8jTLC6dmkeue4DsrLth2tAYhkiaa/p0iW41RR1hkunkp8FcRnRnPg9hHZE1qDIUeBxX8VSiW44gzORUb0lyDkRSvYYFAkH20Xd4Lk1Zv6eB/XVtfQpZEm6+da1R6ttifV6NYZPCHB8jBnX2Oz0Yy7dV0xY1exeySKiYzJ1Q3v1GD6aMKD68abdtR/BaBuG4yfId9fzs8TXM+8ajnPejp3ivDzfnDzZXYkp9tCySJHK9Sp8/p2nbPLi2gbCpINsmqix1XC63m4sm5nUIWYD/rW+kPiYhW3rHOE1R8Mg218wo6hCyAK/taGVVjQ56tPM1NZWrphZ2CFnbtrnt3nfZWx/uNMdSVK4/c7IQsinmrLG5TCnWcLs9qLKEZMTZ2waPrmvoGHPVtEIGecClaaiyhCI5/WsfWd/IgVbnsKPQq3L9cUV4ZBtNUTrWgmJbxG2Zf3/YQDSxUy72qywY7iVqWkRN24nQhsLURCSe29yKnu7Fo8cIRYJcj9OXMp2xcaIxwb4qKvqJHJdjppTptMVTHylsbxWVyhre/sSwoDWe+rZTmuzMeyIhKiOxbSftP5HQIxAIBEeNw1BV6cX7Gyuc3qsHJeGaerALOHFSOa6DpNt25Z31+/suAJNkRpflMWHIoSO8ly0YR6E/0Q7ocLFtx3TKjKMbFq+ureSsHzzNLx9f3n2kMz99ISlMGlbEyNLea4kX7QmypdF02uR0EbJut5tx+QoXTujsS7uyIsSS/UEwoh09Z1UZXG4XZ4zKYUJxp3vG/pYYz29tQbY6BbLb4+G4UhenjXaMhAD+/eZmnl66u7OeWJIpy/PwvctP6BgjSA2qLHHp5AI0jETfWAmMGO/vC7P8QBCAQp/KZZMLUJVECnG7SEXjsQ1NHT1jp5f5OWdcHm63O2mdSUacAyF4aVtLx787rczL+CINXdaIGhZRw6I1FGJ7i8XLXcYNNNyKU5Oa7lhp0INWlR1Bm+kpsqYFrdHU160GXE5N9EAhZjiCNtXRaJfiRIkz/SBBpBwLBIKjzeEpuTRi0aaKg0ZlDxvb5tRpQ7s/2itxw2Tplsq+/11ZZu6EcvyeQ4lsGD+kgD9/8RQ8muKkER8xTsQ2bph89z9LefidLR13WkIxVmyr6aXlTwJJZv6k8l5TlFtjJi9vb0XBShIYmiKjYfHpyfm4E8I/ols8v7UFTVWTamo1VSNfszlnXLJYfnpTCwZqx2trqoJXtrh0cmHHz1LTHOYnjyxNjnzLCl86bzojSw8vei44tkws8TB7iB9XIjqrSqAqMs9saaEt0TNm3nA/00pcSUJVMmJsaTBYtNcRvQAXTMhniF/C5XIlC1pT561dQfY2d4bzzhidg2zpxGyJiGERMSzaQmHe2xtmdYXT+3Yg4tUyR1Q0RlObFu1WsyOiqFuOEEh1D9qBZugT0R0RlupkEE1xetAe5hl82tIiIrQCgeAoklEfiTXNYVbvqOlbVB4ukkSOR2bO+LLud3ple0Uz2yqaD/rvnjz18IQxwGdOm8RzP7yQmSMLHEGraCAd4a8i8bP8/pnVGAm7zk37G9lX19pHKjRgm30K+Hf3tNHWS3qx2+1h7lA/kwZ1dnFfsj9IfVRCtozksS4XC4f7Ke7iVrG6MsTm+hiY8S7j3Jw8IsDw/E53lj8/t4bddeHOlkWSTHmeh8+fPaVjjCD1nDkmF83ujM7KlkGLrvDGTse8SZYkzp+Qh9JljCpLKFi8vqutQ/T6XTIXTshzBHEiiuuMs7FllVd3OK9HIuJ70nA/cUsmqptEdZNI3CBiWDy1uYXGVKqkFJPjyozUQ910RFgqo4q+DBL/ByNqpIewyvdkR/r24RLSnch4qvFlQaq3jTOXA6l/sUAgOHYcoYJKLet211HbEj2IqJQO75JUJg4tPOzWPSu21xA1pN5FoiThVZ2U5SPhrONGsOR3V/HEd87lU3NGUJLrdlKPFVciYnsY31S2yY7KZvbXtwGwaGMltqz2US8rk+/VmDm6s49rO8G4yfL9IcdhWJI6Lk1W0GydM8d2zlNYt1i0J4hkm46DceJSFRW/YrFwZOdYw7J5e1fQqY0E5zUVFb9scnoXl+N9da3c++qG5P69ssrlJ49nSFGg8zFByhlX5GFSiRuXy92xTiRDZ+n+MLVBp6BvQrGXmeW+pDGyZdKmKyze1xlJPX6Ij/FFLtxdxjmvF2dDdZjtDZ07xxOH+/HIBjFb7ojOhqIxqiPw9KbmjnEDjfYU2kyI1MQMZ/Oayt6vuW4nTTbTCScihakmPw3MkfqTtjRwliaR6p2b4tZXHxchaAUCwdEiA7ZAnSzaVNl3rakkU1rg4407L2XlH69m+R+u6vNa+ttP8/Dt53W0gjkU72040Le2lGTGlOUxtryznvRw8bpULj1pHE//4JOsv/tzPP/DC7n9UzOYM64Yj0tJ1Ab39Q87GKaJnijmeXfD/l51LDg/55QRhQwr7qxRbWdtVYSQqfYSlXVzXLmP4Xmdu7/1NRGadTmp/tUZ62JGmZdif2eYaFNthANtBpLZGcF1u93MHuKjpMu4f7+5mdq2eJdDCsdU6ooF4zrGCNKHE4cFEhHVzqirIWks2d8pVBeOCDiR+y7RWdkyWLov2BGdlSWJM0bnoEhWcnRWcvrKvr+n8/UCLoX5wwNEu4jZiGERDId5e3crG2vCHWMHGl4tcwRaRHf6lfZ2LthfZLqRTjvp0LJHlSHX5RgUDRRaY+nhjp2TBanepu0I2lQbmwkEgswmo76CDtrnVZKZObqEM2YO5/hxpZwwvqzPa+6EcsYOPjzxGY4ZfLCl6qB9W+eML/vY/U9L831cMGc0v/78Qhb/5kpW/vEzfO3CaYl+sH0JWomAR6Mkz0dzKMaaHbWdabrdkWROmTasx0mubcPa6kiH82znJYNlMG94Z2TUtm1WVYQTdbJdLkVGMnVmD/ElvfaKigiaqqLIzmurioxixZk3rPM1W8NxHnxrU/L8yjJTRxQxa2xp52OCtGFCiZd8l42mqR3rRbJ01lZFaE0I1QklXobnabg0rXOMbRK2VNbXOK2ZACaVeBmRp+FyuZLWH4bO9oYoVW2du5wTh/mRzBgxyyZqmEQNk4huELZkntzU1DFuIJLjBn+GCNqY6bia9vVRfqxRE86wmRDNPhStMQilOLLlVp31d5hnwxmPZTvRxEgaCLBsyDQw7cQ6ToP5FAgEmUnGfJ1XNATZsLe+7xRjSeqzHvTjsK2iiV3VB6mXtW1Onzm8+6NJ/PLxFXzqp89y+S9e6HF96qfP8cSibUnjVUVmyogi/nDjqVx/5hRQ+ggjSDJTR5ZQEHCzZmctlU2hvn9Oy2DBlCHdH6U2pFPREu9R/+pyaQzJ1Rhd2Hn0WxM0ONAc6zlWVSn1q4zoUgNbFzLY3RhNisq6XC5GFbgZ1iXS+876/Wyvak0W4ZLMaTOGf+wDAsGxIeCSGV/sTTJvkm2LGCqbap3cR0WC4wb70DQ1aa0oks2HVRGshJLRFInjB3vRtM42Pe3jZNXNuupO4VuW42J8sYeorSRHZyMRluwP8mHVwDWDkhL1s5ni2NsWT216oSdhCJXpWLbjtJtqYRVwOetvoGBYzvrV+zg77i/a/+4zPdW7vaZ+ANsfCASCj0HGiNk1O+uob431IdYkMHVOmtxTrH1cPthcSdyS+6hDlfC7ZGYfJIJo2zbPLt/Ns6sqefyD3T2uZ1dX8sTiHd2fBonn1raE+w5hSDKfOnEMAIs2VoDcxzeaJDMoz8txo0u632FPUxzV5UaR7CQx4dY0Jha70boct+9qiqO5vT3GulwaY4o8eLqEOrY3xFA0T9JYl6oytdSXFB1+cvGOnuZXttWr8BakDxOL3U4ddJd1oEqwOSFmASYP8qJZ8aR+srJlUNUap6ZLxHXyIC8eW0dTkwWtbJtsrYsm9ZOdPSRARLeSr7hJxFZ4dsvAjs66FCdC1j37Il1pS3FUMeDK/KgWCSGQ6lpkEtkB2TCfh0vUcARYX1/P/YWmOII201O9ddOpA0+lSZxAIMhMMubj7511+/qul8VGliRu/usbnPqdxzjrB09y5vefOOh16nce49rfvUIkfvBPznfW7+/+UCeSzIShBYwuO3jrGMm2wNR7v+IRnly8nR/8ZzG7a1poi8SpaQ6zeFMlV//mZZ5ZuivZGKnjRRVGFHu5YuEEABZvrupD6Ds/5/RRxZQV+LvfYV9z3EkBTrpkJMtgbFFy6GJPYwylS61kVxEzsktUFmBXYzxJyGqKjGTGGF3YOa45FOPdDQeSWwlJEh5VYurwos7HBGnHsHwXWjcBKlkGla3xDnfhEr/KkFwXLpfWMUaRwOX2squpU8wW+VRGFrpxd2vTI9smjWGDmoSxFMCUQV5MI0bEJCk6G45GeXdPK5WpDPelAX4NAn2caaUbZiJdM5Wb12ypn40azuFAKh2OZSlz3LWPFiE99XXLtGcaeDI/1TtuOi17EtUqAoFAcFhkhJi1bdupW+1LrAGWbbPxQDPvbqzmjbUVvLmu8qDXu5sbaAjG8Lr6/uYNRnRW7ag9aL3sSZMGJ+pae0eSJIaX5B4kXGJjmBZ3Pr6KGV9+kKk3/5tpX/o3p3zncR59f0cf71kCSeL7l8+hJM9LdVOYNTsP0rIo8XN2x7ShIWyg2N16y6oyXsWitMuuOG7a1IeNHiZRmiIjGXEG53aODesWdcF40lhNVSnyqZT4Ow8k1u6qY29Na7efW6I030dpQXL9rSC9yPOolAQ0XGpnGrGCjaRoVLY64lOWJEYWuJPGOILWZm9T8g5wfJE7KYLrjAO3x8v+lk6BOjjHRZFXSaqbjRomkbhOXUxi0V7H2Xsgk5NBAi1uOiIsVemaquw48vb16ZxJhNLA4VhTnAOCgVQh0hZPj3pPf5a0noolWk+lOtNAIBBkDn2rsDRiX10bm/Y39C3W2rFMJ4p5OJdtcOaMYd1fIYktBxrZ00NsdcG2WTj10OmwF8wZ1WuWcic2WAZtUZ199SHqWmOYZuK99Iaicf0ZE7nxnGkAfLir9uAtiyy91z644bhFOG4hkyxmXapKkU8j4O7ckbTGTKKGjYydZNSjqgo5HoUcd+dSagwbRHQLhS4pxprKIL+KS+kct3RrVcKxuQuSTGGOh3y/J/lxQVqhyji/z25CVVNVKls71+2wPFeP9SXbFg1hg2jChRtgWL4bjFhSb1rnokMcA7gUiSF5biKWlBSZdS6T13YO3DY97bS368mUPpSRRMse66CfkccOlwJ5WfJxE4yn3mnXozrrL9OjhIeLlTAwSodoYm6W1C5HDSdCKwStQCA4HDJCzK7aUUtz2OhbrB0xErJtMP8QvWHf31iBKSl91svmemRmj+u7XradS+aPZc644p7CrTu27bzHvt6nJIOi8ZmTR/OXm07reNipl+3jKFySGVzoZ9rInmm7wbiJYdk9nIxVVSHfqyRthtuiJrKsOgK1Wy/aHJeCr8tRfGPExO3xIHd5XVVRGORPfv9rdtb2OrcFAXffgWxB2lDi11BUOWntyJJNQ7hTfJYEVFTbQFWUzjFYxEyblmjnTqXIp5LnUVDVTodkRZaQbIvmqIXRRemU+lUiut2jdjYcjbPiQJD6cB+HQAMIb4ZFaYIpNoTKBlfYdtLBaTfgyqz193FpNzBKZZo3zraEHDd4MyQz42BEEincXc48BQKBoFck2061fcGh+do97/CnF9Y7NaZHA0lh1KAAa/7yWfL8fX/jXvrzF3hq6e7e/11ZZd74Ehb/5krkwziC3rivgU/+5Dl21YbAPMJdmySDrJLjhh9cMZfbLj4etUuE8+RvP8b7m6p7j+TKKufOGsZL/+/i7nfY0xTnmS1BTCP5/WluL5ML4cyxuR2Pra+O8NaeGEY8+dhfdbkp89pcMaOg47Ele4OsqrWJRzt7f7o8Xk4ZpjG9zAuAbljMve1h1uxuSK6ZlVVOnVrG49+9AMt2UswF6YFtQ8CjEfA6u/51VRHe2a+jxzodhxVVJVeDa44rRFMkoobFf1Y3EjIkLLNzfaqai/PG+RjXpS770bWN1ETlpDUmyTKaZPO5WUXkepwDk7uWVfGj96qx9e4hKAkkeO7qySwY0bOf8kDDsqEhknphc7hIQIE3daIynmgZlMoa3qOFR3XmMpWmQJbtzGfwCL/uMpkcNxSkQZTfsKA6mLpsh6OJR4VBPe0+BAKBoIOMELOTvvQQWyq6tW/5OCgaV84fySO3n9P9Tgf1rRFmfvURKhojvUdKFY3bLpjM7244ufudPtlZ1cytf3+bl1fvw5aUhIizkoOTUuI/JNk5ZrWhyK9y2cJxfPWiWUwcWthlMOytbWXGrY/QEukjcq1o/PqaE7j9kuO732FnQ5xXd0XRY8mFVi6Pl+nFEieN7OwHu/JAiOVVVpJwAdBcHobn2FwwqdME680dbWxtImmsqrn4xFhvh6lUY1uUyTf9i5pe0qNdqkJ+oO9DBkFqCMd0fnrNSXztolkA7GyI8fL2EKZhdkTYZVlGlWyuPb4Iv0vGth2R2hCTMfXOXa3m9nDiYJXjuvQmfnVbKztbSFqPkiSBbXP5jALKcpzI/r/X1nPTa9XQQ8wCmodfnVzErXPLut8ZkER0aIyC2ctHQzqiyo4IS1VkKaxDUyT1EbajQcDlCKtUZrnoljOf2XBAcLgUeNMj1TeUWMvZIGhzXM68CgQCQW9khJi9/7UNH+sLWZIk4rpJa8TZTOuGxanTh3LixJ6mSO3UtYT5z5ubsGzb2VB3wzAtLpgzmqkjirvfOiiGafHO+gM88s4Wlm6torIhSDhudEQgNUUmx+ticFGAGaNKOGPmcE6fPozBRZ3Csiu7qlv4x8tre/0ZAUzL5sZPTGP8kM7IaTuba6O8v1/vVcxOLZI4cUTnceiyfSHW1dvEo93ErNvDiBybT4zvjOK+srWVfUGp83UlCVmSOH9CDsMLnG/57ZXNzLr1QYJRo/feBt3b9QhSj6Lxx8/P56sXHQdARYvOs5taIGHSRiKSqko2l08vIN/rRFJf2NxCZUhC7xJxdXl8TC0iaY0t3RdifS9rrP0gZGRi7WyoDfPi1kbUXjIidNPixOG5nDLy4A7jA4mWWOqNgY4Et+JsXFPVM7c15kQUs4F8T+r76UYNaIqmzuSrv0n1gUxXWqLO3382kA5rWSAQpCcZIWazlZhuUtMcpjkUw7ZtbNvG41IpyvFSmONB6WWzfjTZVhdj0YHexeyUIpgzrKvQCLOlCWKRztRhEmJ2WMDmrHGdaZ2vbG2lMiSjx53XlSQJ27a5aEo+ZTnON/zK7TXMu+3hRMRILMGMQFb5x5dP5YvnTAegJmjw7IZmSPx+aY+kYnPx1HxK/M7v+o3tbcmHGwkxO6HAZn4XMbu+OsqqGqvXNbZwmMa4YrGT+SiYlpNunEnRMZ/mCIJUmFhZthPRSgeH2o+LIjvRWd8h7BqONWEd6pP/rLMaVYayQOpNsGyctZwNqd6y5AjaVJUhCASC9EWEv1KIW1MYXpLD9JHFzBhVwszRg5g4tJCSPO8xF7IAHq27c2zn1b1ZhVtxNpbdx6my1KOthSL1HKPKEnYX0RqMxjGRnX9GcloNiSuNrl6xKcntTAuWurhVd7+6HpF5VKeFU/IYMLrlcroUCdnu+ZqaLGFmQ65cilBkZwPY1281HQnr0Jqi6KgsOUK6Hz6Cjzmm5USZU/3n49MGVlTNsJxodKrnXcI5zMiGVknth0yxDDqUEwgE/UNGRGY31oZZdqANNRt2F/2ApjgCoDdMy2bO0BxGFXioatV5fUcYy7aSUn1dbg/DAzYLRnVGzTbWRFlbaxHvFsVVXW6K3BbnTuiMzL67K0hFSO4yVkJRFU4d6WFYvnOs+u76A5z746dwqVnwLZtFtKfkh2O9haUklvz2Ck5M9CyuCxm8tLkVkiKzMhIWn5yc15Fm/MHeEHvaFGLdDMFG5tjMH9Epjnc0xFhWYSSlI5NYj7PLFMaXDKDd8DGgKZJax+CPQmEKDaHa4s6cZQN+FxSmuH7Wth1TIj1D6rePBrluJ5qYaiK6I66zwRnYpThzmim9tAUCwbEnI8TsO7tbeHpzfVKPUkHvaLLE4soIH1TFoIt7bOcAN79cWMxtJ5bTGDZ5blNLD9dgzeVmkNfinAmddbC7GuMs3hfvITRUTSNHs7loSl5HJOODvWF2tJBU+6i5PZw0VGVMwgAqGjeoaQ73WesrSA0el8pj72/lln+8l+yOLUnkejXW/OUaRpc59agVLXFe3R6CRIo8HTWzcMnUPAKJ3sPv7Gxjf1Ah3sUQzO31MS7PZu7wTjG7pTbGiiozaRwJMTtnsMoEIWY/FnHT2dBmUmRDlR1Bm6qNa2OWpGiSJjWHEQMaw9lhsHU4pEuaN1lWC+5RnfWcqrp6gUCQXmSEmBUcGY+sr+eml/ZiGz13YZLm4TsnlvDdhUOJ6Db/W9uIbslYXdrjKKpGrsvmkql5HdHwqjaDF7a0YZlWUo2rrCi4ZZtPT8/Hl+gDsaYywoe9tOaZXSYzLdGaR5C+PPT2Zq75/evdxKzCuPJc1t51DV6Xoyx2NMR4d08Mo4tLsbMeLC6fUYBHddbDq9taqQjJ3WpmvcwokZk1pHM9fFjlrJveamZPGa51HIQIPjrBuCPQMgmf5mxcE8upX4mbTr1xNpgXKTLku50obSrJNEOyj4tHdQ5kUrF+u2LZzmFWqOe2ICNJ5eeCQCBIL8THQBYyZZAPv2zi1RS8qtzjaok4OzOXAj6XgilJGJbdccUNg5aIQaRLPpjfJYNlYULSWN0wCcZMwvHOsTlumbiuJ40zTJumSAaFhAYwNU3hnvmIkszk4UUdQhagNWpiSUrS79lCxutSkrIogjET3TC7rRsLr5b8bwRjFrppJa8bG2KxOG5VRPCPBgFX6sXMkRLWUxcddSmpS3M+2piWkzodT7Ewz3GBPw0ilf1F1EiP9H5ZcuY+W6KZYd2JNotojEAgEGI2CxmV72ZUgReP5sKtKMmXLFHZ6qQKK7JErkfGQu4hNGK2TF2oU3z6NQm3JvUca1rYikZDuHOHlO9R0PU4pt0pfHXToqnLGEH6cqChrYcBGJLEnAnJvVvrQwa6mSxSTSR8qtSRcq6bNq1Rs9s4iOlxchJpyO00hHR0I1nMmhZYtoUvW3ZgaUBAg0QSRcYQjKfOXTjHBd4sEV9xM/WpprLkHBAMpD/pYCx167crLsVZz9lyNBiMO4JWIBAMbDJsSyM4HHLcCnOG5OBxaXhUOflSoDqoE0k4QZQF1CTx4FwWlqyyv6Xz21eRJYp8CpaULGYdASNT0dp59JznUfBqUtLYuKFTHzbRB0qxVAazo7K5Z+9fU2fhlCEd/9eybWqDeo+Iq2lDcaAzetsWM2mLW8S7jDMBGZtcd+duVjdtGiMmccPotrYkVNnG1y2KK/jouNXMi85aNrTFUhdVzHVnhyMsiUhhqgWAW004bA+QP2ub1K7frvhdkJNFFRutsdRlbggEgvRAiNks5bRRuT3Si72qjEeG5qhJVSLvaXCuRiwWwbBBt6yOK6br7G6MJbVEGZbnwrCsHlfc0NnXFMNKCCBNkSgNaJiSkvR6TVGD5mgafJsL+iQY0dm8vxHsLraXkszo8jxmjCrpeKgtZlEXNojq8c51Y9uEIxHKczqVUkPYJGbLxA2jY5wpSQTcMoEuYrYlatIQ1onpetI6NCWJPI9zECM4egRc4E2RqdJHJW466ZqpOA5zK05EO1sIxh2H21QScGVPCvfhEDfTR3QFXKkzVTva2LYjaMMpXs8CgSB1iB1ilnLisBzKvODtHp1VQNHcrK92THaKfCpFPg1LTq59jMfjVLbGqe2Sajws34VlxDGQ0C2744rFdaqCelJa8qhCV1LUTjctTEljX3OKQwKCg7K9sol9dW3JYlZWOHPmCHJ9nTvP/S1xYraa9Du2kPG7ZEq7RGb3Ncd7qatVKMvRkvp4HmiNY8haz7RlW6I81RasWYgsQcCdeb1UQ3EnwpUK/K70cKU9GhiWI6xSnSiT43KitAOFYArXb1dU2TFPyrA//z4xLMfYLhtaDwkEgiNHiNkspcSvccaYfLweN25V7nGtqHTErCJLjCt2914Lq7rZWNNZYFXgVRiS58KWtW5jTSzFzbb6zm/pEQUu3LKVJGR0y2Z7fYoLtgQH5Z31B9BtOcmxGsvg8oXjug5jc20Uo0tNtGHZ2LLKsDytw6zJsm12NkZ7pA7HdZ1RBckhma11UQwr+fUMG6LRKMMTvYkFRxevmpnirC3utHjpb2TJSc/MlnTjiJH6SKEqO4I20w5VPg5tcYilQYKSS4G8NOiBe7Rod2sWglYgGHgIMZvFnD8uD6/U09XYI5lsqYtQHXTyciaVeLDNOCbJrsaxeJx11ZEkp+LpZd4e0TMj4YC8vjpMe1ZywKUwvtiFrXTW5MbjcXY3xWkRqcZpy4srdifXy8oqx48tTaqXbY4Y7GiIEY/Hk4VnLMaU0s5WOzVBg4rWOPF4p7O1JSn4VBia26mimiMmOxpixLq+XmJsrkdmcM4ACt30MwFX5plBmZYT3epitt5vZGO6carTM32aI2gHCkZi/Xap4EkZOe7scpaO6M7cdrd8EAgE2U2GbWMER8LkQT4WjMjB7/UmpRq7ZbBkjSX72gAo9quMK3JjK65kgarrNMZhXU1n389xRW4K3GDLycZRsXic/a06Oxo7o7OzBnsx4jFM2xHJccMgZClsE9HZtOTDXbUs2VwJdpfDBkni82dNwaV2hqM21EQIWUpSxNWWVcpyVEZ2ibhuqIlgKe6kww9bVhlX5MLn6vzoWVcdJoKK3i2Ca0sKE0s8uEW97DHDpWRmdDaawqhiNqUbm4l041RHs3Lc2TOnh0Mq2011RUrMfTY5S7fF02NuBQJB/yF2iVnOpZML8Cs2Xk3Doyodl1uyeX9PG2HdES7zhvkw9FhSOx1HgJos2hsimtjtuBSJ2YO9mN3Skg3TwpY1PtgX7Pi3y3NcTCp2Y6udIlk3LVZURLDS4VhakMRDb28hYkidx9qSwuhBAa48eULHmLhps+xAuIfwNCybecP8qIl8wahhsaoiRKxLv2HTljD0GLMG+zpeL2bYLD8QJq53czG2JUw9xozyzrGCY4M/Q+sWg/HU9O9sby2TLWcsqTwYaKe9B2q2pHAfDm1pYMJFe7pxltkSNEXTwzlaIBD0D1nydSzoiwnFXk4blYPflxyddUkWLbrCB/tCAIzIdzOt1IutupNERTwepzoMS/Z2itQ5wwIUe2xsJbl2NhaLsr46wq7Gzp3RaWNywIx3iN9YPM72hgjbu0RwBannQH2Qh9/dClaXYkRJ5tZPzqQwp7Owak1VmMqQlZQSbCsaJV6JaV1SjNdWRaiLkpSKbKsa44vdDM3rjN6urAhRFbaSU5YTYycO8jA4ZwCFa1KEKmdmqqFtOyIsFfWHnkRrmWyhNQaxFNQhd8WtDqx0Y9Nyev6mQ0qsV8uu9UxC0CbO6gUCQZYjxOwA4FMT8xnksfG5Xd3SjW3e2tVGa2I3eOaYHFQMTORkt2I9zhu72qhPuBW7FImzxuQQ1010m45xcdNClxRe3d7cYR9UnuNi4YgAlqI540ynh+3bu5wUZ0F6cNcLH1LVHO10MZZVjhtVyA1nTekYE9Et3tjZ5vye29eHDXHD5IzRObgUJyobMyze2tWK3mWcYUuYepzTRuV0vF4wbvHGztbk1+sy9pQRgY6xgmNLwOXUg2YaugnNke6P9g8BlyMCsoXmWOrTjbOpZczhoFvOvKcDgSyLjMcS/ZRFEphAkP0IMTsAKPSpfHpKAR5FwqspSdHZKBpv7HSEZWlA4/SRAaxu9bBx3aBFl3lmc1PHa84o9zGz3APdIrnRWIx1tVFWVjgRX4DTR+dQ5rE6anKjsRgfVof5sKqzFleQOjbsrecfr2wAq/0YW0LG5qfXzCfg7Tyuf3t3G5UhJ1rf/vtGdTNtkJvjuqQOL9kXZH8wOXqL6mLuUD8jCzrz2V7f0UptTOolKutizlA/I7qMFRx7cj0gZaCrbMxMTbsTWQKfmj3tTWJpkG4MkOvOnhTuwyGUBiZcJNKNsy0yHkoYQgkEguxmAH1lDGyOH+zj5BF+Aj5fUnRWs3RWVYQ7TJlOG53DqFypR7pxNBZlWUWERXs6I6qXTM4nVzWTxa9pYdgSz25upiXqRHK9msynpxQgYzrpxqaFhcKzm5s7anEFqcGybX744Ac0hfXOqKyiccPZUzj/hFEd4/Y1x3lpWwuxeKzjd21JCl7Z5OLJ+R3jGiMGL29vS+4/K6vkKAbnjM/tGLetPsrrO1uJRTtfr+vYc7uMFfQPXjUz041J1M+mIk3W73KubCEd3I2zLYX7UFi2I7rSIYLo17JrPSMMoQSCAYEQswOI8ybkMzpXIsfnx6PIeBTH2ditaby2I0hr1ERTJK6eXohPNpN6xBqmhW5ZPLqhib2JvKgCr8qVU/PBtpLa+sTjcaoi8NSmzkjuuGIPnxyf64hZG2LxGHuDNq9sa+7yEwr6m3tf3cAzy3aBmdjByipTh+Vz57XzO8bETZv/rmskbModItUxCrO4dFIuJV0U0JMbm2iIQTxh/GTaYJoWl04pID+RPxiMm/x3XSNRS0pu89Q+dnJ+x1hB/+LXIJEtnlHoCVfeVODPwPZGfWHZibZHKa419GsDK904ki7uxpIz99kUGbdSWFsvEAj6hyz6yBIcCrcqcfGkPEo8NoEuhlCabRC1VF7b0YZh2QzOdfHZGYXY2EkiVdcNWnWJf66sozXRK3ZGuZ+LJuQ6wreLE3I0GuXNXUHe7xLJPXtcHguHeTrcjePxGM9tbWFzXYqK3gY4q3fW8L3/LO7iXiyT41W5+0unUpLXmTb85MZGNjcaxGLRzgiq4uITY3I4cXhnDez7e1p5d2+QaJdxtuLijNEBZg/xA2ADD66pZ3ebRSyWHJW1VRenjfRzwlBRK5sq3CoEMjS7O5QiQeDO0PZGfREzU+MS3RVFdlJeE+boA4JQmgiubIyMx01ojTqmWwKBIPsQYnaAUeBTuXhyLgVuCHg9HYJWMWNUh2UW73FqXWcP8XPZpLxEJNVGt5zIbDQWYVerzT0r64gnvhkumFjAqcO9XVrwWBimiWHb/HttAzsbO/vKfmZGEdMKZWzVTdwwCJsS966sozmRkizoH+paItzwpzdoaIsn0oslJEniD184mYVThnaMe39PG89tbSEWi3asAVt1c1yJyqenFnaM29MU48G1Tc6hh2k64zQ3kwoVPj2loGPcM5uaeO9AhGg00vF6HWMLFC6f1vmagtTg1zK372Qw7kRp+xt/lhkXhfTUpxtno8PuwdAtR9CmA9lmbgYQSRhCCQSC7EO544477uj+oCC78bsUygMqla0Giqoh2xaqLKFg0RSTkGyb8lyN8cVe4obB5kYTyzKxbBvLBsPQqYrINIZjzBocQJZgaqmP6pYo+0JgGAaWbWNaFnFUNteGmDMkgFeTUWWJ4wb72VwbpD6uoMejBC2NypYoc4cGUAbSUXyKiMQMrvndK7y3qTrRikcCReUHl8/mG5cc3zFuQ02YvyyvI2ZYmJaJZYPs8jKxQOFr88twJ3LRmiMGv11cQ3XEwtDjWDZImofhAYmvzy8lkLDJfWNHC/9e2+isD8vCsp0UsPaxX5tfSk4mWupmGe1/gpEMPF8ybSf639+RUlly/ozSoW/o0cKyHUGTSlMwVYa4NXAianHTec+pPkySJGdNxwzn7ylbiJvO+8rEvtoCgaBvhJgdoOS4FcpzFKrbDBTNjWybjqCVoC4C2BZlORrTSn20hGNsa7YSAqRd0BrsbrNpDEY5fkggIVJ9VLVE2BuSMNsFrWnQaqrsqA8zb6gflyKjKRKzyn3sqAtRG1OIx6JURiAY0zl+sJOOKjg26IbFF//6Oo8v2d1ZJ6to3HzOZH57wykdG9ddjVF+taia5riVOJwASfMyIV/h9oXlBBK7rahh89vFVWxuMtHjUWec6qbMJ/GtBWUMCjiq4p3dLdy9wonmtwvjjrFeuH1BOaWJsYLUo8rOxi8T/dmMFAkCl+LMVyoiw8cCw0r9xl+WHLfogZS4YyVSfVN9rqvJzmd0OqQ+H01ihnPYpYi8RIEgaxBidgDjdykMzlWoaI4Tl90Yuu6IVcumOuz89+BcjeOHBIjrOhvq4x33LdvGNEx2tdk0BGPMHuJHU2SOHxKgrs1JRW6P5pqGQV1MZldDmDlDHEHrUWXmDQuwtzHEgbCEHo+zrckgFjeYWe5PaTQgW9FNi6/d8w73vrElIWQlUFx88axJ/OWm01ESzj87G6L87L0q6joirRKSy8OMYoXbFwwmz+OohLhp88cPqlhWraPHIglx6qHMB98/ZTBDcp0cwbd2tfDn9giv2Slk0TwM8UtJYwXpgSQ5v6NMFRGWnRpBIEsQNTvL0DMd03JEeioNgVyKE3GPZ5mo6gszcYiQDmnr7YdaZpas53ZMGzxK/38+CASCY4MQswMcryYzPF+jqiVMi6kRNwx000I3LSpDNqGYzrB8N8eV+/HKNquro5hIHVFa0zDZ2WqzvznMrMF+vJrMnKEBorE4Gxp0R7hYFqZhUBWR2VEfYu7QQEeE9sRhObRFYmxu0LEsi42NcWJxg1kiQntUicQNvvCn17jvza2OkJUkkBW+duE0/vh/p6Ildqvb6iP8v3cqqW0XssigapwyxM03FgzuSAOOGRa/X1zFuwdiHUIWzcPoXJkfnjqkQ5w+vamBu1bU9xCykuZlfJ4ztjzbmhtmCZqSudFZ03aWeH8LAlV2hGy2RLMsG2TZaduUSpQsFVV9YdipP0SgPTKehenGhpWazweBQHBsEGJWgEuRGFvkJhKNc6DNxEQirpvohkltTKaqOcLIQjfTy/yMynexqiJIFBXT7Ewl3huS2VAd5LhyPzluhVmDAxS4ZdZUh9FRME0T09Cpikhsrg5ywlCnhlaRJU4YGiDPJbG6KoRuy2yoi9IWiSfqccXR6celriXMZ37zMk8u3ZsQsgqKInPnZ+fx02vmo8jOjmlVRZD/904l9VEbQ49jyyqSJHHVlDxuntNZI9sWM/nl+5W8VxHDiEWxkEDzcHyJwo9PG0qJX0M3be5bVcu/1jZiGGZnajEyqC4WDHHzvVOGUOgTu4l0RZKcDWwm1s6SMNRRZUeU9yfZVufZPo/9nbbdFUV21mKmZgocKXai9turprZmmS7p89kWGY+ZjphN9YGBQCD4+AgxKwBAliRGFrop8MjsaogQk1zEdZ1YXKfJVNlSG6IsR2NqqY85Q/1srmmjLq460TbLxjR0aqIyi/Y0M67IS1nAMZCaXuplXXWQZlPDNAxMQ6c6KrNifwszyvzkJ478J5R4mVHmY21VG82GzKYGnb0NIWYPCXSIKMGRs2FvA5fe+Tzvba5xhKysUpjj5r5bzuTm82cgJXZKL21r4heLqmmJ25h6HDQP+S64/aQyLp5c1HGoUNUW50dvHWBlrY4RjSYEr8ynJ+bwjQVD8LsUGiIGd75zgJd3R5zU9YTZk624UCS4dno+X5lbhidbmnNmMYrkiJlMjM7aiau/040z2UCrL2wbPFr/zmN3XLITmU11D9z+QrccEZ8OnniK7ERnrWwKzyYyOFxqZvbWFggEnUi2nS3VPYKjRVPE5MUtTWxqMAEby9BRNDeaZHHWmAAnjQgQNSz+ubKGxzc1Y9oSluGYCcmqhkeBG2cVc/m0YmQJmqMGf/mgild3BZ0NpmkgaW6KXDbfXlDOSSNyO/7t5qjBX5dW8cquEJLmYZzf5AenDmV0YYY2v0whj7+/ja/8/W1qW+OOa7GiMXNEHvd99RPMGjsIgLhpcc+KGh7d2IxlOdt/SdU4vkTl9oVDGZbXmQK8uirEz9+toDoCZjyG7PJQoFl8dV4pZ43NB2BNVYhfvV/JgRCYcaclkyRJyC43g7023zxpMHNEH9mMIhiHxgxuBZ3nhjxP90ePLbYNDZHUt7c5muR7IDfFH8OGBVVt2ZXyejAUCcpzUnuI0E5LFFqysLVNjgsKvN0fFQgEmYQQs4JesW1YURHipa3NtFkaZjyKJMnIisLEAoVLphZS5FNZURHkt4sq2BOSsPU4tmUhyTKSonLqMC9fP6mcsoAL24Y3d7Xwp6VV1EZlLD2GpCi4ZInrZhRw3axS1C7f2G/taubPS2uoMTzkEOHr80o5d3xnv1JB37RF4vzowSX85cV1mKbTQ1aWJW48ezK/uG4BBQFnZ1/RGufOdw+wutbAMmJIqgufbHL9zGKuml6Cljiutmx4bH09f19VT8SwsW0LSVY4sdzFNxYMYViem7hp8+CHtfx7bSMxS8LS4yA5hxuybXPe2By+Mq+cglQX3wmOGMuG+nDmpngqEhT5+r8+TjehKtj90cxFlqA8kHoX2GwVVX2RLmLLsJwDmliGfg70hSRBgWdg9TQWCLINIWYFB6Ul5kRpP9gfwZAVLD2OqrnxygYXjM/jlNG5RHSL+1bV8Mj6RmIoWLqz05BdHkpcFl+dV8YnxuUjSxK1IZ2/L6/mhe2tGMhgGkiqxvzBbr69cAhDu7jaNkYM/rmyhqe2tmJKKueNdPHVEwdT1N9NJDOIDzZXcsvf32bV7iYnGiurjCz28tsbTubSk8ZBIvXy1e3N/H5JFQ1xCduyAJv5g718ff5gxhR1hrGqgzq/XVTB2/sizkGFopKvmdx8QimXTC5ClmBzXYRfv1/BugYD29A7xK6kqIzJlfjqvOTouyDzyPTorE+DYl/3R489TVFoyyLhFXBBYYqFlW1DZdvAMYMCKAuktma5nUz/HOgLt+IcGKTDHAsEgiNHiFnBYbGrMcrTm5pYXxcHSQbbQpJkxuZJXDWjhNGFbrY3RPjTkkrePxDBlmRsI44kO+GQ00f6+dr8wYzMd/LUlh8I8pellaytc/LwJFmmQLP4xvzBnD+hICmtam11iL98UMXKBih2Gdw2r5RzxiWPGeg0h2L88rHl/OWFtYTjTrqwW5W4/ozJ3PHZEynNd3by1UGdPy6p4OWdIZBkbAlG+OHLc8s5e2x+x5zawItbG/nDB9XUx2RsLBTb4rwxToS1PMdFW8zkgdU1PLy+iUh7NFaWkVUXearB52aWcOW0Yvxih5DxWDbUhTLbpbfIC/5+jr7ETKgPZY/wknCi3Kk+T8xWUdUXPs2Z91R/5VmJ9PlIFqXPt+PXoDAN5lggEBw5QswKDhvbhrXVYZ7YUM/2JsOJvkkSim2wcLiPy6cVU+BVWbS3lbuWVbO2NgayhG3oyJqbgGJx3cwiPjtjEDluBd2yeXFLI/9YWcPeUOIfsUxOHe7jmwuGMLqgM0Jo2fDGzmbuXl7N9laYV6byjfmDmTwoBeGWNMKybZ5esoMf/GcxW6qCjgy1bRZOKuXnn1vAgilDANBNm6c31XPX8lrqDQ3btihWDT53XAlXTC0m0MVlZFdjlD8sqeStfWGQVWxT54QyN1+ZW84JQ3MwbZtXtjVx1/Jq9gbpqJeWVI2AbHLxxAKunzWIMtFyJ6vIdAHhUR1B0N9mL00RaIt3fzRz8apOdDaV6caWDQ3h7DLZOhRFPkdwpZqw7gjabNw5FnggJ8V14QKB4MgRYlZwxJgWrKwI8uyWRjbWObWWIOEjxvkTCvjkxAJy3Aqvbm/mnyurWVfvRHNty0SSVUYG4Mtzyzh/QiGqLNEWM3lmSyP/XlPLvmBCFBHl+uNK+NxMR/i2EzMdIfW3FdXsbopxyeRCvjS3jGGpdiZJAcu3VfPjh5bw6poD2JICWEwdmst3L5/LZQvHoyV2mysrg/xuUQWrGyyQZArlOFdMLeIzM0oo6bI7ao4aPPhhLQ+sqScke7HjUaaXaPzf7DLOGONEbZcdaOOvS6tYVhkFCWzbRlJUcmSDiyYVcu3MEkbm97PbjqBfyPTaWVJkYhQznM1/JjpC90Uq5rE7Yd1ZjwMFb+IwJh0ykhoiEMqiA5p2VNk5qOnv+nqBQPDxEGJW8JGxbFhXHeKZzY2sqAhjqB4kSSJPivKpSYVcMLGQHJfMO7tbuXdVNR8cCGEpGpLtRA+nl2jcNKecM0fnJUStwdObGnlwbS0720BS3YzwxPnKnDLOn1CQ1KInali8vK2Jf6ysobI1ziWTC/nC7FKGpnqH1Q+s31PPb55cyWOLthOzVbAMJpbn8LVPzeIzp04k4HUiotvqI/x1WRUv7w5jyi4GazGunFbMZVOLKQ10itiwbvHslkbuWlZFleGFeJjZpW4+f3wpZ47JR5Ecl+K/L6/i7b0hDEnBth1jqcFeuHRyEVdMK2ZIl3pnQXaS6dFZTXFqZ/u7K1RzFFqzqHY2VfPYnWwVVX1R6E0Po6JsPKBpx6c585wOhwYCgeDwEGJWcFTY3RTlxa1NvLWrhXpdQ1Y1AnaYCycUcMnkQkr8GutqQvxrTS2vbW+mxVKdelojzvGlLr4wu4xPjM1HlSUihsXrO5p5aG0ty6vj2KqbyTkGt84bzJlj8jtcdkmkzy7a18q9q2rYUhfm1FH5XH/cIKaWZl/68drddfz52dU8+v52wpaKZMaZPbqIL18wk0sXjCfgcQTq9oYIf1tezTPbWjEkhemFMp+dMYhzx+WT1+XIOaJbPL25gb8vr2ZPzIXbjHDmqByunTmIecNykIDlFUHuWVHFm7uDmKob27JQrDjHlXq5YloJ54wrIN8jamIHEpVtmb2JzXU7kcX+JG5CXdjJaskWUjGP3cm2muRDkapU+d7ItgOaruR5nJZeAoEgMxBiVnBUaYkaLNnXxotbm1hdE8F0BVDjQc4YlcPVM0qYWOylPqTz7NZGHl9fz/q6KLbbj23EmVogc91xpVw4sZBct4INrK4I8sj6Ol7Y1kyr5WJKAdw0u5Tzxhfg1ZJF1Oa6MI+ur+e9Pa0MzXNxxdQSThuVm9EGRKZls2RzJX99fg3PLt9DzNbwyXHOmTWSL547jdOmD8OlKtg2fFgd5B8ranhxRwseReKcsblcNX0Qc4YEktoeNUcNntzYwD9X1bAvLDPEY3LplCKumlbMiHwPcdPmjZ3N3LeqhqWVEWyXDzseZohP4tzxhXxqUiEzywNpsaES9D+ZHp1VJCj2Ow6m/Um21c6qshOdTfXHazaLqt5IhxRvEq2nGiLOQU22ochQ6AFvGtQoCwSCQyPErOCYYAN7mqK8sbOF13Y0saXJxEJiaoHC1TNLOGt0Hj6XwvqaME9srOfl7Y3sbgM0D2VKhMunFnP19BLGFDpH/w1hnVd2NPPIujpWVAQZmuviulmlXDG1mEHdXDFaYyZv7mzm+a2N1Id15gzN4YLxhUwt9SWJunSmJRTn+WU7+dtLH7JkWwMAE8sDXH3aRK48eQLjBjs9d8O6xWs7mnhgdQ3rasJMKvFyxdQSzhmXT2m3fLSt9RH+82EtT2xsIKqbnD4mn6unl3DKyFxcikx1MM7/NtTz37V17A6rgE2JGueUkXlcNLGQE4flJEV2BQMTw3JqFTN5E5uKFjNx05m3TI5qdycdeqDGTccMSs+ieT0YLsVx5u52lpsSWmPOYUI2ki4O0gKB4NAIMSs45hiWzY6GKG/sbObVHc1sqY/i1yTOHJPH5VOLmT0kgI3E6oo2nt3SyMvbm9jVaqFgsWCYn2tnDuKssQXkuJwCrZ0NEZ7e3MhzWxtoDBucOiqfq6YVc8LQHFzdwoX7W2K8vrOZxXtbMSw4YaifU0flM6HIm5SunA6Yls3aXXX8562NPPreVmpaDQbnu7hwzmiuPHkC8ycNxqUpWDZsqAnx9OYG3t/TiluVOXNsPheML2BsoRepy9tqjBi8sbOZxzbUs7U+wrgiD5+eUsy54wso8KhEDYt3drfw4Ie1vLG7jbgtM8wvcdroPM4dV8CcoTkUeoWAFSTTEoWWDI+GFaegxUy2RREVydnwp/qMK5tFVW/kuZ1U2FRjJlylM9kU7mAUeJ0DG4FAkN4IMSvoVyzbqa99f28Lb+xsYVNtmBy3wqmj8rhwQiHHDfYjAVvqI7y6vZlXdjSxrjpEwKVw5ph8LplUxPzhOR2pw1vrI7y0rYnF+1qRJJg/LIezxxYwvtiL1iUKa9uwvzXGor2trK4MEtYtxhZ5OGFIDlMG+VIm2Gwbtlc28cwHO3h80XY27qunvMDPubNH8al5Y5g/eTA+t4YFbK935u3DqiAWMKs8wKmj8hhd4EbuomAbwzorK0O8uauZrfURSgMuzhqTz+mj8yj0qsRNm2UH2nhiUz1v7WyhNWYweZCPM0bncerIPKaU+vF2MdsSCLqTDTWgXs2JcPVnskY8kZqpZ3BUuzupiHJ3x7CceY1lqajqTrqkeJPlrtKyBINz+vczQiAQHDlCzApSSlPUYG1ViMX7WllXHcKybaaXBTh5ZC6zBgfIcSk0hg1WVLbxzu5WVlW0Ydo2M8oCnDY6jxOH5VLsc4RoZVucD/a1sr42jGnZTCj2MqPMz6gCD4Fu3/rNEYMNdWHWVoWobIvj0xRG5LuYWOJjZL6bAo+Kcoy+wSIxgy0HGnlr7X7eXLuPyoYgQ4sDnD5jOGfOHM7EoYW4NIWwabOrIcL2hgjVbXHcqsyYAg9Ty/wUdRHfhmVT3RZnc32EzXVhaoM65Tkujh8cYHqZD5+m0BQxWFkZ5L09LWyqCyMhMa3Uz4IROcwsC1CUmEOB4HBpjDj1s5lMKtxhW2JOZDtbkCQo9qa+vjCkO1HCgUI6GHCRJS27DkaO2+k/KxAI0hchZgVpg21DXVhna32ELXVhGiMG+R6VsUVepgzyMsivIUkS1cE4m2vDbGuI0BgxyHMrjCzwMLHYx/B8N5osEdYt9rfE2NsSI6qb5LhVSv0agwIuAi4ZtyJ3pOPaQF1IZ3dTlH0tMRrCBooEhV6V0hwXQ3Lc5HtVclzyR6q5jekmdS1hdte0snZXHXvrWtENkzHl+UwfVcLEYcUMyvOg246BVmNYpy1mYlk2PpfCoIBGURdxHTctIrpFQ8SgPqzTHDEAiSKfyvA8N8U+DSSoCcbZ3RRlV1OUYNwi160wttDDmEJvyiLRguwhYkBdqPujmYVbdaKz/ZmIoCdqZ7OpxtPvcuYxldi2E50N693vZCdKIjrb30ZmvZHppnAHQ8JJpe/vkgSBQHD4CDErSGvCukVz1CCsm+R7VEeodSNqWLREDcK6jSZDWY67x+ZUN23ChkVUt9AUiTy3ctDIq27ZtEZNWqIGMdPCrcoMznHh6f7Ch8CybSrqgzS0RZ0IRo6X4jwv7oR7hwWE4haGaSFLoMoSLlVOSpHuipUQvIZl41Jl3IrU42cybSfybNo2LkUi4FI+kggXCA5FXcgRtZlMKuoPs7HGMxU1yN0JJ6KzA2VTkw6HCCS+l+pCTqukbCSdWiIJBIKeCDErEAgEgo9EKO5EwzIZTXY2qv1Zf2hYTs1xNtXOpov7a3144ERnAcoC/bt2+6ItBk1ZdkDTlVQcegkEgsPjyMJMAoFAIBAk8Lv6N0X3WKBb/S9+VBmyLdM/rEOkn+exNwKugWXYky7u2D6Xk7afrYT07K0LFggynQzfhggEAoEglfS3gdKxIKz3f9/cbDgI6E4w7qScphKPmnozqv4krPf/YUxvKFLq08yPJYblZKKIXEaBIP3Isq9SgUAgEPQnXjXzRZmRguisloXR2ajhbPhTjd/xwBswpEvduldNj5TnY0VIdy6BQJBeZPgWRCAQCASpRFOyQ5SF9f53GM5xd38k82lLAzHrUZ2014FCOJ4eKbCqnN3RWRKCtr8/JwQCwcERYlYgEAgEHwuv5vQbzWTa0wj7E1V2oojZhGGlR/9hrzpworM26VGvTMIILJujs7E0yT4QCASdCDErEAgEgo+FRwVPFmxgU1HzmY2RrLCe+vY4Pm1g1c5GjP6v++4NVXY+D7KZ1lh6zLVAIHDISjFrmBab9zdS2RjqfiutsGybfXVtrN9Tz/764GEZCzSHYjS0ZngvjAQHGoJsOdCI2d+7x4/A/vo2thxoxDqcX5LgI9EWibN+Tz0toTSx5xQcEdkgHCzbaTHSn2SjYVHUSI9IYTYeFPRFKuq++8KnZX4d/aFIFxdpgUCQIX1mv3z3m6zeUYumykiShCJLWJaNZdvYNmiqzD23nsXY8nwAqptCjL/xAa44eQL/vPWs7i+XFuyra+Ob977L88t3EzWdlKhPzRvDr65fyLCSnO7DO7jkzueoagyx6DdXomR4/4Grf/0Sr67aw7Z7r6coJw06vx+ES+98nkUbK9h1/w34Pcdmh1TdFOL6P7xKOGbQ9c8y1+dmcJGfM2cO55Nzx+BxZeex93PLdnLRz17isW99gssWju9+W5DmGBbUhTK/nkyTodjv/Hd/0RaHpuw4o+zAp0Gxr/uj/YsNNAygvrOa4sx5f67dvmiOZr/gK/I6ruQCgSC1pMFH3qGpaAyxo7qZXTWtbNrfyNsbqli1q55dNS3srG5hV00L8W7d5y07fS3UwzGdK3/1Eo8v28+ssSV8+dwpTB5WyCNL9nHBT55lb21r96d00NAWo7YlOzqT24nfUyZg2fYx/1mjusk7G6tZtq2mY23vrG5hxfZq7nt9M1f87k0uufM5Gtuy4/ffHdsGJDnl6YmCj0a2pBfqVv9HFX1a9vXojKRBX05pgEVndTN9hHuOK/trltPB7EwgEGSImH349nPZdd8N7Lz38zz9gwsBmxvOmsS+f93I1nuuY/1d1zJxWGG3Z9mJK/14aeUePtjRyOdOHsX7v7qCv958Oh/87iq+ddEU1h0I8a83NnV/Sie25VxZQ3r+jnrSD+vJhmg0yhULx7H7/i+w5R/XseUf17Hj3s+z8e7PcvEJw3h5XR0/fXRZ92dmCc4cZ7qR0EDmGCUt9Dth3Yk09xeKlB0HAV2xE/OYarwDrXZWB7Mf125fKHL2O0rHzf4vSxAIBD3JCDHrc2vkeF24NQVfIsXSpSrIkkSO10WO14V8GDtgy7Y/shxxInMf9dnJbDnQBLbNpxeMR06kCmuqzI+umscT3zydb1w8q/tTOmgffyQcrZ/dSev+aK/TnhL+cfg4vz8Szz8UdmKuPsr7/Lg/XzsuVUFT5I61neN1MXFoIffccibD8xQeX7Sd5kPUldr2x5/zo7FujsZr9MWxfG3BR8OjZocoi5upic5mW51hRE+9UY4E+LJgTR4ucTO9+s5mO/198CUQCHqSgV+dRy7m3lm3n8t+8QLjb3yACTc+wGd+8xKrdtR0H9aDSNzggdc3cO6PnmLCFx9g3Bfu59RvP8bvnlpF/ccwYSor8IEss35PfdLjfo/GpSeNI+DteZz5mydX8uW732R7RTMNbVG+9o93uPmvb7J2d133oQCEY50/+/gbH2D8jQ9w7o+e4tH3tmLbsGlfA7f87S027E3+GQBeWL6L2+97j9ZwnKZgjJ89uowTvvZfxtxwP3O+/jB/fHY14dihvy0P1Ae5479LmHfbI4z9wv1M/dK/uf4Pr7J0S5Uz4DCESGNblD8+s5pTv/MY475wPxNufIDLfvECL63cDcDiTZV87R/vsK+uLel5Ww80ccvf3mb5tmo+3FXLuT96ilGfv4/v/2dR0rh2dlQ1851/LeKErz3M2BvuZ/qX/8PNd3XOr3SQw5LtlU1c/euXGHvD/cz8yoP8/H/LP5aJUV9CujjXy+ThhVQ3BWmL9J7ftHRLFTf++XWmf/k/jP3C/cy69SG+8re3WL2ztvvQXmkNx7n7xQ856wdPOn8vX3yAc3/0NPe/toFQtO/d/Z6aVr5+zzss2VwJwIsrdnHpnc8z8Yv/YvyND3DG9x7nr89/+LHmBaCqKcSd/1vGSd98lHFfeICJX3yAi37yLI+9v424keJdswApIWizgbAOZu9/iscETc6+CKJpp0d01ufKnnV5OIT19Cjh8WrZP+8xU7TqEQhSTQaK2cOjXXv89JGlnP79J9m8v5H5k8oZU57HU0t3c9r3nuLVVXu6P62DqsYQ5//4GT5/1yJW76xlTFk+E4cWsqe2lW/+Zznzv/m/TlF2hHziuBEM8sv84vGVvLV2X/fbvbJ0SxWvrtpLUzBKOKrzxod7eXX1Hmqawt2Hsr++jXN+9BSfv2sRK3fUMqo0lwlDC9hyoJGrfvcGV/7qRVZsr+Wvr+9kR1XP+tx3NlTyxxc2sGRzFSfd/ig/f2w5g/K8LJg8mFDU4OsPLOMzv3mJaLxvQfv8sp2ccNsj/L/HPqSxLcLkYYWUFfh4ZulOTvrW49z94ofIsnRQkbh6Rw0Lv/UYX//XcrZXNjN+SAGjy/JYvKmS83/yIl+/5x0+2FrDn17e2sO5endtK399bTt/fHYN593xHDuqWhhaFGDK8OKkcQAPvbWJE77+KL96ai2hqM6UEUUU5nh56O0tzP3GY9z/2gZUufc/lZqmMJ/86fM8s3wPM0eX4HUpfP/hFXziR09/bOHWnXDMYHdNC4U5XnzunrveXzy2nAXffpL/vL2VHK/GtBHFKDL8/ZWNnHj74/zmyRXdn5LEhr0NnPztx/jyP5eybncdo8vyGF6Sw4rtNdxw9yLO/sGT7Kpu6f40SDhT//GlzSzZUsVX//E2F/z0JZZurWJMeR7jBuezeX8jt9z3ASd/+/FeD1AOh/c3VjDvtkf5wcOrqGsJM3l4ISMG5fL+pkqu+N2bfOqnz1Hb3PPvQdC/uJXM7zlLYpPa30LMp8FHSL5JayJpELmSyL5+vgcjaqRHdFbKEpfzQ5EubZEEgoFKRrgZd2X1jlqO//oj3H7xcfz68yd3vw0JV9ipN/8b3bRBVvj9DQu4/qwpHanIK7ZVc86Pn2FQvo+Vf7iqhzttJG5w4f97ljc3VPOjy47jtotnk5ewrIvGTZ5YvI1b73kfn1vmvV9+mtFljovykfDwO1u4/s9vokjw28+fxM3nzTzoBtC0LGRJ5pRv/4+KhiCb/n4dqiIhS3LS8xraIpzzo6dZubOBH15+PF//1PEUBNwAROMGTy7eztfufR9NhqrmKE9/7zw+deLYzhcAvnX/e/zmqdUU5XqZPrKI+796NiNLcyHR9uib973Hn17awt++OJ+bzpue9FyAt9ft5/yfPE+B381f/u9kLpgzGpfqNKGsb43w88eW8/dXNuJWbDyayoa/XdvDzXhHVTOnf+8p6lqj/Oa6+Vx31hQCid9TKKpz76vr+eHDy8lxy1Q2BFn6+6uYO6G84/mvrt7LOT9+FiSJG86cxF9vOhWPS8W27SQB/dSS7Xz6l68wclCAu28+jTNnDkdVHOFa1RjiRw8t4eH3d+BRbLwula33XJ+0Xv756ga+eNdbvPSjCzl39igAHnp7M1sPNHLHZ+YfkeP0nppWRn3+Xr7wiWk9XLhbw3F+8shSfvf8Jq5eMIL/fvPcpPt/eX4Nt973AadPKeUv/3cqk4cXQSLKu253PV+95x3e3VzH3V88iZvPn5H0XID9dW2c/v0n2VXTxs8/O5ebzptBnt9ZNw1tUX7/1Ep+/tRaZo0s4LWfXkxRbvLva8nmShZ+63+U5vsJxix+fs1crj9rasdctUXi3Pvqer7zn6UMK/Lz5p2XMGKQs6baeXbpDj7181d47Ftnc9mCZDfjrQcaOeW7T6KbFn+7+VQuPnEcWiIns64lwh+fXc3Pn1jDubOG8swPLuxYb4LUUJ8lDrIe1XGHPYI/449NQyT7Ij35Hsh1Pk5ShmU76zLVplT9hVd1XLn7cen2im5CbTg96niPJbluZ50LBIL+p/dwUxYgSRKtMZtffm4+N5w9Namm9oTxZdx64Qy2VLSyYnvPdON/vrKeNzfW8cPLZvH/Pju/Q8gCeFwKnz1tEg9/8ywqGsLc8fBHM+O5+tSJPPmdc8j1anz5ng+45rcv0djWd+qyIjuitb1mVlXkjse68qvHV7Jydyu/unYeP/ns/A4hC+BxqXzmtEn871vn0BLWQXJaHXVHkiRQ3ZTl+3jiuxd0CFkS/+4PrpxLsdfmicXbk55HQjB/8/73wbZ5+vvnc8n8cUnCojjXy++/cApf/+QMmqN9bxJ/+OAH7G+McP+tp/OVC2d2CFkS6dhfvWgWd/3fyVQ29j1nSDJD8t3cec38jnY2Xd9vcyjGtx5YTGFA47kfXsg5x4/sELIA5YV+/nnrWVx/xkQaI73XK9e1RHDJNtNGlnQ89tnTJvHTa046IiHbjktVeGnlbs778dOc86OnOPfHT7Hw9v8x7sb7+d3zm5g5LMDPrz0p6Tl7a1v50X+XMXVIgCe/d0GHkCXxfmeMLuHZH36SWSPz+cFDH7C/W0o2wI//+wE7asL87aZT+PZlczqELEBRjoc7P7eAOz8zm9V7W/nDM2uSntuOZdlUN0f451dO4ysXHpck+nO8Lr7+qeP5x5dOZWdtmB8+tCTpuYfiR//9gJrmCA/ddjaXL5zQIWQBSvK83HntSfzk6hN4+cMqHnp7S9JzBf1PtqQWRo3+F+XZ6L6bDqZEspSdc9sXEQMOoxromKMpA6d2NiaiswJBSshaMWtYFmMGebn29EndbwFw8tShIEnsqGxKetwwLf795mZKAxJfucCJYNmJNj/tF8A5x4/i/OOH88wHO6hsCCa9xuFywZwxvPvLTzN/XAH/XbKf8378DPsO0paHg9RTArSE4/z3na1MG+LnlguP6367g9NnDOezp44H+WDfMBI3nzeNwpyeR43FuV6mjihmZ1UzkW6pxu9uOMDq3c186dypzBlflnSvK9+6dDaji73ovexwdla18OTi7Zw3czBXnTKx++0Orjl9MufMGtb3+5AVTpk2lNKC3psdvrJqDzvrInzz4uOZOqJn+nE737v8BIq8ztrozszRxcRtlTv++8FBa0oPF0mCllCMLQca2XqgiS37m2hoizB9VAm/vfYE3vj5p3tENJ9cvIPmqM3tl8wm3+/usV5tG/L8bm771HE0hi1eWpmcXr+/ro3HF23n9CmDuPGcaUn3uvKNi2czfaiff7+1idZwL6EjWeWCE0ZyxckTut/p4HNnTOG0ySU8uXhHjzrnvthW0cRTi3dw/vHDO6Lf3d8fwE3nTqfEB/e+uiH5BQT9jkd13Eyzgf42gvKq4MqyxIKYmR4RUX8WpnEfjGAvH9OpYCCkGhtW/x98CQQChyzZbvTEMC0mDyvqkULcTr7fDbbdY1Ne3RRie0UjraEY5/7oKY675UFm3Zp8HXfLg8z+6kOs2FFLmy477sQfkQlDC3nlp5fwmZOGs2xXC5f/6qVDOtX2xY7KJiqbwpx/wii8h2haeMGcMWBbvYpj27bB1Jk1dlD3Wx2UFvhoaosQ69bfd/XOOrAtLuqWutydPL+bk6cOwejFYWXT/gZ0VC6ef/DXABLipufupP19jS7L636rg2Vbq8GIccEcRyD1xeDCACdOGozeS+HXOceP5Itnjee+t3cw97ZHeGrJdkyr57jDJaabfPqk8ey49wY2/u1zbPzb51h397W8/rNL+cYlx1PUy+HCiu01YBn84vHlPdZq1+vnj60AWWHlzmTTsK0VTQR1iU/OHd1rpL4dt6Zw/gmjONAQ6lE72z7fn5g1Iunx7kgSfOrEsYQNmS37G7vf7pVN+xowkFm5vZrZX32oz7/JT/zwCYIxixU7PlpNruDoocrZE42JGP0vaLskA2UN4TQQs5IEgSyc276Ipkl01qMODEEbiqdHrbJAMNDIWjFr21CS13tErivdN++hmEFUNxlVlscJ48s4fmxpr9fM0YO4eN5ovnjGWMed+GOQ43Xxr69/gsvmDWPZrhZ++9TK7kMOi2DESR0eUhTofqsH+QF3ohNgT2wbsE3yfH0XObk1BcPq2fqloTUCttWr6OrO4CJ/94cAaA3FwLYozT/0vBbmuAGrV0FLIorcF23hGD6XTEHg0D9rSZ631zYwsiRx95fO5J83L6A5FOPSX73O5b94gbqWg6Q/HwJNlZ2UOLeKz632aT7VTms4iiaZzJtQzvFje1+zs8aUctLkcm48fTTzxicfUjQFYyBJlBb0/vvoyqB8HyARjnXf3UuH/Xt31qdES2/R3V5oDsUBJ126+/vq/h6vO30iN57VdzRf0H9kS6oxQLSf0wc9qtN7NpuI6OkRufJqfX1bZB+W3f9rtzckwJNl2Qa9YaWJe7dAMNA4+C45wzlIkKlPcr0uFFli2shi/v6VM7n3q2f3ef39K2fyj1vOSqpR/Kioisyvr19Inmbw6LvbPlLKamGOByyDHVXN3W/1oL4l0qcARAJsq8+o9sEYXOgHWaHiMFKv99f1PqYo1wuSzN7DSEOtbY4klnFPoUliXvuiJN9PWLepbkp2Qu6NmuZwn72MFVniC5+Yxrq/XsO3PjWNp5bt4wt/fq37sMOmt2j5wcj3e1FkmTs/dxL/vPWsHuu0/brnlrO455azuOHsqUnPH5TvBds6rHT5ivog2Da5PQ46bJDkwxLxO6uaAYvi3EMLXxIHCUgyl8wfxz9u6fv93fvVs7n7y2dw95fP6P4SghTgyaJ02Wg/O/JqMnyEj9+0Jx1Sjd3KwIgSthNJozY9Wt9fx1lDKA6hI9++CQSCj8EA+Gg5MsoL/Rw/rox31h04ZC/Zf7+xiUUbK7o/fFAM0+L1NXupbenZQmRocYBRpbnUtUY+Uqrx2MH5jBucz3PLdh6yLcxzy3b2aQD1cZg7oRwsi/+9t637rSQaWiO8s35/kpFPO1NHFBHQLB59d0uvdapdcfrNHnxMXyyYPBhkF08s6mlk1ZW9ta0s3lSJSzv4zrwwx8Ovrl/IVy+YynPLdrNpX0P3IceEBVPKidoab3548DZPK7fXcP/rG3q0VJo0tJAin8rTH+zAPMiuJxwzeGHFLsaU5TC6LLlu11lHNi8m+v/2RUw3eXzRdor96mEfAk0bWYxPtZw1exDaInH+8twa9tQcvO5c0D/IUvaIBt3q/1TjbEnT7krEcNxtU002zm1fxNOkXlnN0gOa3mg7+PZLIBAcZXoqCQFf/MRUakIWv3qi73Tf55bu5Lo/v8VdLx2Z2cxLK3dz9h0v8PP/Le9+i/31beyqbqW8wH9Y6Zrd8bpUbjpvOrsbDX7yyNLutzt4YcUuHn5vG1hH/xtu3sQyzpg+mPvf3MxLK/oWNj/733L2N+movbhxDC3O4drTJ7F4exN/ef7D7rc7+PtL63hzfRWS/dHE7OnThzJ7dD5/eHYN7/dxKGHZNnc8vIyWuNSrO3FUN3uYYI0fUgBIRPtp13bJiWMZkq/x00eXU9Wt3247dS1hvvjXN/n6vYt7RKIH5fv43BmTWLStkX+8vC7pXld+8dgytlRHufn8Gb32uZVsi1fXHOC+1/r+m/jjs6tZuaeV68+cfFhp5ADDS3L4zKkTeWl1BU8s7vuQ5NdPrODWf63k5YP0jxb0L+6Dn/9kFP0tCNxK9kS22zGt9Kgp9GpwCFuJrKK/125fDJRDhLgporMCQX8ixGwvfOa0iVw+bzi/fW4DX777LfbVdqa71jSH+e2TK7nyt68yvNjHTz8zN+m5h+L0GcM5aXwJf3pxE9//92KagzFs22bz/gZu/PMbtOoq1581uaOVzJFy83nTueC4Mn7/wmY+/8dX2VHZ3JG2Wt8a4ddPrODKX73ipAP3YQD1cVBkmT/eeAqDcr1c9quX+dOzq2lsi0JCGG7e38DVv36Je17bRLFP6jP96Sefnc8Jo/K57f4lfOeB96nsItL217Vx+33v8c0HFjO21P+R34PXrfHHG0/G7VK56GcvcN+r6zsi2qZl8+GuOi76yXM8uWQH5TkKZi9mVV/882t85jcvcaDeWSNbDzRx1wtrGVYSOKj51NGktMDPb69fwPbqIOfd8QzvbzjQYVYViRu8vmYvZ//wadbsaeYX185lZGnPn+v7V8xhzuh8vvz39/juvxaxv64Ny7YxLZsdVc3c9Nc3+NmT6zhnRilfOq9nn1oHm7FlOXz1n+/znQfeT0o131/XxtfveYfv/Gc5J44t4LuXn5D0zENxx9XzmDA4h2t+/zp/fX4NLYlGnJZts62iiRv//Do/e2It588o4fozJ3d/uiBFZJMgixj9K8QUObvqjttJB2ElS9k5t30R0R2BlWo86sARtP2dySEQDGQk+6MqgRSxakcts7/5FLd/ahq/vi6532Y7VY0hxv7ff7hy4Xjuu7X3+rk1u+qY9Y2n+d3nTuC2T/VsY9MSivGlu9/k4fd34dMkxpbnIgG7alpp02Vmj8zl/q99gmkj+27p0hc7q5q57BcvsGZfkBIfFAbc7G0IEzVlPrtgJP+89aw+xewp332KAw1Btv39ml4jhST6p97yt7d46L0duBQYV56LS1XYUdlMW9Tg9ktmMWtsGVf98V2e/tYZfGre6KTnf/P+RfzumQ/Zc+/nGDEoJ+leO9f+4XWe+mAX+++/LqmXbTurdtTw+T++xroDQQo9MKo0l1BMZ1tFM3k+N/d99SyeXLqbF1fsZsc91/Yaia5sDPH5P7zCq+tqCLhgTFkOlmWzvbIZ04LffH4Bkqzw1fuX8sEvL2LehM5WQK+s3su5d77Bn6+bzS0X9iW+HN7bcIAb//IG22rCFHslRpXl0RyMsqO6jbJ8L//6+tnc98YW3lpXwZ57r02qJb7172/xl5c2keOWGVbsZ09dCMO0+ffXzuDKg7QV6o09tW2M+sK/+fyZk/pctwfjvtfW8837FtMSMRhV4qUwx0NVY4iKZp1cj8wvr5vPzX0K0a7zXY1PtRlWHMCyLPbWhYhbElecNIq7vnRGr7+rxZsqWPDtJ/jLF09BNy2+/a9FqLLEuMF5gMTO6lZCOlx4/BDuueUsynoxm3pm6U4u/tWbPHrbqVyxcHz322w50Mhnf/Myq/a0UOyXGTkoh1BUZ2dNkLgJV540iru/fMZhGXoJ+o+mCLQdntdX2pPjgoK+PeWOOlEDantPtshoBvlTLyZ105nbXs4os5J8D+T2/Krud9pi0OScb2c9Jf6BI94FglSScWK2vjXC44u2cdzoQcybWN79NgDhmM6j721jTFkep0wb2v02JF7nf+9tZeGUIUwfVdL9NiSic+9vPMBzS3exeX8DumExsjSXs2eN4PwTRn8kg6R2moJRHnh9Iy+t3E1LKEZZgZ+rTpnIZQvHox3EtOi5ZTsJRXWuPHniQQ2uLNtm0cYKnl6yg037G5Alx9TqsgXjOWF8GXtqWnl++U4unDOGkaXJ9Y9Lt1axdlcdnzltEoE+3uPb6/azq7qFz542CXcftaRtkTjPLt3Ja6v3cqC+jTy/i4VThnL5wgkMLQ7wxod7OVAf5MqTJ/Qp3uOGyetr9vLc0l3srGrG7VI4YVwZV54ygYlDC9mwt5531x/gsoXjGdTFvXp/XRvPLN3B6TOGM+UwajObgjGeWrKdNz/cR3VTiKJcL6dNH8ZlC8ZTkufl1dV7qG4K85lTJyaZStm2zZtr9/PIO1vYU9vCyNI8bjh7KvMnDU56/cOhLRLn4Xe2MHFoYZ/r9lDsqWnlicXbWLqlisa2KIU5HuZOKOfSk8YdVqRYNyzeXLuP55ftZHtlE7IkM3FoIRedOIaTpw7t8wDFEbNP8pcvnsxXLpzJpn0NPPruVlbtqCFuWIwbks9F88ZwxozhfZpy7alp5cUVuzh39qg+f9ZgJM5LK3fz6qq97K1tRVVkJg8v4qJ5Y1g4dUifJl2C1BHWob6nRUBGosqOEOul1P+YURfOvihPfx8K9EVjJH16sR5r3AoMCvRp+9hv6CbUhNLDlOpY43dBURqsc4Eg28k4MSsQCNKP7mJWIGjHtKE26JgoZQMFXkeM9RetMWjOskiWljgU6ONcq9/IpoOWw6HYB77ez6f7lfrwwGhhI0vOnKc6C0EgyHZS/FUiEAgEgmxGkaCPxIuM5CN0TftYeNXUi76jjW6lR/9TrzawhEYsDeqVyTJjuINh2dmXVSEQpCNZ9hUpEAhSgwSy2ke3X8FAJ5s2r/1tBKUp4Mmi+WsnHYygpISgHShEjP7tl9wXniw8oOmLiJEe5lsCQTYzQD5OBALBsSTX52LOmHzHJVsg6Ea2bV77Ozrbh3VBRhNNk02+R3EODAYCRgr6JfeGpmTXAdfBMKyBkVItEKQSUTMrEAgEgmNONtXJ9bcRlGU7zrvpIP6OJunisJtNjtuHwqM6azfVBOOOAddAQFOg2DtwDk0Egv6mn76KBQKBQDCQyaZIjGH1b6pxtvZFjRikRWlCoB8NvVJNukTE3YqzrgcCupk9B3kCQToixKxAIBAIjjnp4KJ6NOnvzak/y+aPhCFRvB8PBfpiIKW9koK12xuaAu4sPKDpi2ia1CsLBNmIELMCgUAgOOYocnZFF+Nm/7rDZqvgCvfjHB6MbFqbhyJdeusOpDmPmelheiYQZCNCzAoEAoGgX8imzattOxvU/iQbjaCCcWcuU002rc1DYdnpEZ0dSKnGJCLiVhqsdYEg2xBiViAQCAT9givLIov9GZkly+qO27Ht/q0/7gu3OrAEbX8fxPSGS8m+z4SDETVEdFYgOBYIMSsQCASCfsGj9p8DcH8QMcDsxzo4t5qdm/902eAPKDFrpEeUcCD1+SVN6pUFgmwji7YVAoFAIEh3sk0w9OfmVMrC6DYJYWWmgbDyqCANkLTXuJke0dlsNDY7GGFdGEEJBEcbIWYFAoFA0G9kW6psfwuCbDsMANCt9HA1dinZOb990d9p8r2RrW2nDkaoHw/ABIKBwP8HAL+bJWP7cI8AAAAASUVORK5CYII=" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![image-3.png](attachment:image-3.png)\n", + "\n", + "# MEC Tech Talk \\#15\n", + "\n", + "# Tutorial: Exploring Edge Native Apps\n", + "\n", + "\n", + "## Objectives:\n", + "+ Understanding Edge Native Apps\n", + "+ Explore both queries and subscriptions / notifications." + ] + }, + { + "attachments": { + "image-4.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "## (1) Initialize the MEC Sandbox\n", + "\n", + "MEC Sandbox is an interactive environment that enables users to learn & experiment with ETSI MEC Service APIs.\n", + "\n", + "\n", + "| ![image-4.png](attachment:image-4.png) | First, open a browser and go to the ETSI MEC Sandbox https://try-mec.etsi.org/ (Use your github access credentials to login.) |\n", + "| --------------|-------------- |\n", + "\n", + "\n", + "### We will perform the following steps on the Sandbox:\n", + "\n", + "1. Launch the Sandbox enviroment by selecting a network to be deployed.\n", + "2. Choose a MEC API in the MEC Service APIs menu.\n", + "3. Create a new MEC App\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "appInstanceId = \"\" # <-- add here your aplication instance ID \n", + "mep_id = \"\" # <-- add here MEP (MEC Platform) to be used" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The MEC Service base path\n", + "\n", + "The service base path can be used in your MEC application to interact directly with the MEC Service API in your MEC Sandbox. It is composed by the **Sandbox URL**, the sandbox user **Authentication Token**, the **MEP ID** and the **MEC API** and **Version**. \n", + "![image.png](attachment:image.png)" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You will find the \"MEC service base path\" box in the \"Try-it from your User Application\" section of the the [MEC Sandbox](https://try-mec.etsi.org/).\n", + "\n", + "Find the ```AUTH_TOKEN``` in the \"MEC service base path\" and copy it:\n", + "\n", + "![image.png](attachment:image.png)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "auth_token = \"\"\n", + "\n", + "mec_base_path = \"https://try-mec.etsi.org/\" + auth_token + \"/\" + mep_id " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import libraries\n", + "We will import:\n", + "- the ```requests``` library that will allow us to make HTTP/1.1 requests to a specified URL and query MEC the REST APIs \n", + "- the ```json``` module, a standard-library for data interchange between JSON and Python data types.\n", + "- the ```pandas``` library, for data analysis and manipulation in Phyton.\n", + "- the ```re``` library, for using regular expressions (RegEx) in Phyton." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import requests, json\n", + "import pandas as pd\n", + "import re" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "## (2) Initialising the MEC App using the MEC Application Support API (MEC011)\n", + "\n", + "For this part of the tutorial, we will use the MEC Application Support API ```mec_app_support```, version 2." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mec011_app_support_path = mec_base_path + \"/mec_app_support/v2\"" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB5oAAACLCAYAAACwTpWqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAHEvSURBVHhe7f1/VFTnvff/P3HEKCSCrehdippyNGq9sY3kpGM/Qc4nJAdtDjRVTyJZnhBNjUZtpJ9UThNcVfMVvW+4s0JSNRBOJKSuYFI1KRwrnpSsCq46zQnayknVW0oiEnoEW8Fm0DKOfv+YPcNmMwMzCIrm9Vhrlrj3NcM11772NcP1vn6EdXZ2XmWAXb3a9ZLmn0VEREREREREREREREREZGgICwvz+3MwwgYy0OwNKl+9etX3MB8XEREREREREREREREREZEbzxtYDgsL8z3Mx/syYIFmc3D5ypUrXLlypdsxBZtFRERERERERERERERERG48c3A5LCyMYcOGMWzYsB5B594MSKDZHGB2u92+hzXgLCIiIiIiIiIiIiIiIiIiN5Y1wGyz2XwPc8C5N9ccaLYGmS9fvkz9xVYO/PUEdR3NXHBfsj5FRERERERERERERERERERusNG2kSRExJJ6xzQmj4ph+PDhQQebByTQfOXKFS5fvszly5f59V9P8ca5D1kQM4u/j/oaY4aPsj5FRERERERERERERERERERusPOXL/Kf7Z+wp/UIT4y9l3+4YwrDhw9n+PDhvmBzINcUaPbOZna73bhcLk51tLDpv/+D5++cx6SRX7YmFxERERERERERERERERGRIeb0pT+z+dP9rPsf/8iUiHGEh4djs9l6ndU8zHogVOZlsw/89QQLYmYpyCwiIiIiIiIiIiIiIiIicpOYNPLLLIiZxYG/nsDtdnPlyhWuXu19vvKABJq9wea6jmb+Pupr1iQiIiIiIiIiIiIiIiIiIjKE/X3U16jraPYFmQct0Gx+cW+g+YL7kvZkFhERERERERERERERERG5yYwZPooL7kvdZjP3FnDud6DZy/vigX6BiIiIiIiIiIiIiIiIiIjcHIKN/4Z1dnb2niIA74u73W46Ozu5dOkSSz97m5KvP2FN6nPm0nnKz/2Ous8/429XLltPyw1027DhJNz+VdLHfpMJI8dYT4uIiIiIiIiIiIiIiIjILW7JH95gx1cfZeTIkYwYMQKbzUZYWBhhYWHWpNc+ozlYZy6dZ8unv+SjC6cV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QsKEiK42akQEcTPmkLkul1Wm+zA0Ecx6djM5j0xmnHFdw0dPJGlZDq9k38Pt1uQh60e9D6HNIXI6y7blkbO4+zX1pn2zoCvYJkGyTWfZ9myW3TeRyF5XNIkgYVUeb25ZRNKUqK421hZO9JSZLHwmh5wFgYPJN5VRM1lZsJq5U7z3n/dezaNwlbFeZwDBtDkhU70XERERERERERHpl1t7RrP0W6Dr2DcXjo1Pkf8hMD6N/B3zB3jfpMCzpvwLNb3cNBr28vQPKmgB7M++xtr7gw86+Ga7jU0lv3TRgNTRgDPcAgg1vXwxtFXk8mRhfeAZzddQ70VEZAAFOQNRM5pFvsg0o1lERERERG5OmtEsN865Dyg39vu0L04bkACeiD91/27s4T0+jQUhBtvGefewPlfDm3sacQVYUljkenKdc/DGW569l5kx2W/7eS31XkRERERERERERERkIGlGs/gV6DreeKHOUA41vXwhuJspz8qh1Ly59jXWj1BnKIeaXm5hvn2ZvSJIWV/AynsVSB4q+tpT3aqvPZNFREREREREREREhirNaBYR6Y0tlvQX81ibNrlrb3CRG83Yo3vZls0KMouIiIiIiIiIiIjIkKcZzeJXoOsoIiIiIiIiIiIiIiIiIrcmzWgWEREREREREREREREREZFBo0CziIiIiIiIiIiIiIiIiIiERIFmEREREREREREREREREREJyXULNN82bLj1kAxRulYiIiIiIiIiIiIiIiIi0pvrFmhOuP2r1kMyROlaiYiIiIiIiIiIiIiIiEhvrlugOX3sNxk1LNx6WIaYUcPCSR/7TethERERERERERERERERERGf6xZonjByDM/d+R3uGT1JSzMPQbcNG849oyfx3J3fYcLIMdbTIiIiIiIiIiIiIiIiIiI+YZ2dnVetB4Nx9epVrl69itvtxuVycenSJZY07aLk609Yk4qIiIiIiIiIiIiIiIiIyBC35A9vUBK3iJEjRxIeHo7NZiMsLIywsDBr0muf0ex9YX8vLiIiIiIiIiIiIiIiIiIiN49g47/9DjSbXzwsLIxhw4Yx2jaS85cvWpOKiIiIiIiIiIiIiIiIiMgQdv7yRUbbRjJs2LBuceBAAed+B5q9vC8+bNgwEiJi+c/2T6xJRERERERERERERERERERkCPvP9k9IiIj1BZoDBZi9BiTQPGzYMGw2G6l3TGNP6xFOX/qzNZmIiIiIiIiIiIiIiIiIiAxBpy/9mT2tR0i9Yxo2m63brOZAwjo7O69aD4bi6tWrXLlyhcuXL3P58mV+/ddTvHHuQxbEzOLvo77GmOGjrE8REREREREREREREREREZEb7Pzli/xn+yfsaT3CE2Pv5R/umMLw4cMZPnx4n8HmAQk0e4PNbreby5cvU3+xlQN/PUFdRzMX3JesTxERERERERERERERERERkRtstG0kCRGxpN4xjcmjYhg+fHi3Gc2DGmjGT7DZ+7hy5QpXrlzxnRcRERERERERERERERERkRvLG0QeNmyYb5tk7yOYIDMDFWjGFGz2BpzNAWYFmkVEREREREREREREREREhgZvINkacDYf68uABZoxgs3ef83BZQWZRURERERERERERERERESGDm8w2RpcDibIzEAHmr3MgWUFmUVEREREREREREREREREhh5zUDnYALPXoASazRRoFhEREREREREREREREREZekINLpsNeqBZRERERERERERERERERERuLcOsB0RERERERERERERERERERHqjQLOIiIiIiIiIiIiIiIiIiIREgWYREREREREREREREREREQmJAs0iIiIiIiIiIiIiIiIiIhISBZpFRERERERERERERERERCQkCjSLiIiIiIiIiIiIiIiIiEhIFGgWEREREREREREREREREZGQKNAsIiIiIiIiIiIiIiIiIiIhUaBZRERERERERERERERERERCokCziIiIiIiIiIiIiIiIiIiEJKyzs/Oq9eBAunp1UF9eRERERERERERERERERET6ISwszHooaIMSaDYHlxVoFhEREREREREREREREREZesyB5lCDzgMaaPYGla9evep7mI+LiIiIiIiIiIiIiIiIiMiN5w0sh4WF+R7m430ZsECzObh85coVrly50u2Ygs0iIiIiIiIiIiIiIiIiIjeeObgcFhbGsGHDGDZsWI+gc28GJNBsDjC73W7fwxpwFhERERERERERERERERGRG8saYLbZbL6HOeDcm2sONFuDzJcvX6b+YisH/nqCuo5mLrgvWZ8iIiIiIiIiIiIiIiIiIiI32GjbSBIiYkm9YxqTR8UwfPjwoIPNAxJovnLlCpcvX+by5cv8+q+neOPchyyImcXfR32NMcNHWZ8iIiIiIiIiIiIiIiIiIiI3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zgmJPPa9exg3sp2T5fspP9HebaUOERERERERGTo0o1lunHMfUG7s92lfnDYgATwRf+r+3diHdHwaC0IMto3z7mF9roY39zTiCrCksMj15Drn4I23jCWKZ0z2235eS70XCZXrTDUFzxlLnI+2syxDQWYREQnRzMWe/akx9vzO20XVhRjSQ9zPW0RERERERIYmzWgWvwJdxxsv1BnKoaaXLwR3M+VZOZSaN9e+xvoR6gzlUNPLLcy3L7NXBCnrC1h5rwLJQ0Vfe6pb3dwztPzsSz5iIpkFG0nXHpoiIiIiIiIiIiK3PM1oFhHpjS2W9BfzWJs2uWt/U5Ebzdije9mWzQoyy9Dg2/dZQWYRERERERERERHpSTOaxa9A11FEREREREREREREREREbk2a0SwiIiIiIiIiIiIiIiIiIoNGgWYREREREREREREREREREQmJAs0iIiIiIiIiIiIiIiIiIhKS6xZovm3YcOshGaJ0rURERERERERERERERESkN9ct0Jxw+1eth2SI0rUSERERERERERERERERkd5ct0Bz+thvMmpYuPWwDDGjhoWTPvab1sMiIiIiIiIiIiIiIiIiIj7XLdA8YeQYnrvzO9wzepKWZh6Cbhs2nHtGT+K5O7/DhJFjrKdFRERERERERERERERERHzCOjs7r1oPBuPq1atcvXoVt9uNy+Xi0qVLLGnaRcnXn7AmFRERERERERERERERERGRIW7JH96gJG4RI0eOJDw8HJvNRlhYGGFhYdak1z6j2fvC/l5cRERERERERERERERERERuHsHGf/sdaDa/eFhYGMOGDWO0bSTnL1+0JhURERERERERERERERERkSHs/OWLjLaNZNiwYd3iwIECzv0ONHt5X3zYsGEkRMTyn+2fWJOIiIiIiIiIiIiIiIiIiMgQ9p/tn5AQEesLNAcKMHsNSKB52LBh2Gw2Uu+Yxp7WI5y+9GdrMhERERERERERERERERERGYJOX/oze1qPkHrHNGw2W7dZzYGEdXZ2XrUeDMXVq1e5cuUKly9f5vLly/z6r6d449yHLIiZxd9HfY0xw0dZnyIiIiIiIiIiIiIiIiIiIjfY+csX+c/2T9jTeoQnxt7LP9wxheHDhzN8+PA+g80DEmj2BpvdbjeXL1+m/mIrB/56grqOZi64L1mfIiIiIiIiIiIiIiIiIiIiN9ho20gSImJJvWMak0fFMHz48G4zmgc10IyfYLP3ceXKFa5cueI7LyIiIiIiIiIiIiIiIiIiN5Y3iDxs2DDfNsneRzBBZgYq0Iwp2OwNOJsDzAo0i4iIiIiIiIiIiIiIiIgMDd5AsjXgbD7WlwELNGMEm73/moPLCjKLiIiIiIiIiIiIiIiIiAwd3mCyNbgcTJCZgQ40e5kDywoyi4iIiIiIiIiIiIiIiIgMPeagcrABZq9BCTSbKdAsIiIiIiIiIiIiIiIiIjL0hBpcNhv0QLOIiIiIiIiIiIiIiIiIiNxahlkPiIiIiIiIiIiIiIiIiIiI9EaBZhERERERERERERERERERCcn/H/HOwgXszBguAAAAAElFTkSuQmCC" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Notify the MEP that this instance is up and running\n", + "- The confirm_ready method is used by the MEC application instance to notify the MEC platform that it is up and running.\n", + "\n", + "\n", + "![image-3.png](attachment:image-3.png)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/confirm_ready\"\n", + "payload_dic = {\"indication\": \"READY\"} # <-- Message content\n", + "method = \"POST\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZMAAAAvCAYAAACWulRZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACESSURBVHhe7d19cFRlni/wb6chsvbooHeq9zppKtCWyNadtUazU4CG5o6yOpt1QkQJ8bY1kp250WWBnQACorAlccC4EBS4jJMazAxFjyFASBg2I17QIgaTlJvBq5SXQQ2k6BR3urwSvbYLCd25fzzn5TlPn3O6O4QQ4Pup6iL0efr0c5637v6d5zzH09/fPwgiIiIiIiIiIiIiIhc56hNERERERERERERERCqP08zkwUHxtP4vEREREREREREREV0fPB6P5V/YBZMHBweNRzKZNP4mIiIiIiIiIiIiomufx+OBx+NBTk6O8bfH47EGk/XAcSKRwMWLF5FIJJBIJJBMJo3tRERERERERERERHTt0Wch5+TkwOv1wuv1YsyYMfB6vanB5GQyiUQigf7+fvR86cHhqA8nv/CiPyHvkoiIiIiIiIiIiIiuVbleYPKtCTwQiCP/24PIzc0VAWU9mKwva9Hf349T5wbxyw9vRtEdgyi4LYkbvOruiIiIiIiIiIiIiOhadCEBdJ3NQcsnHvzjXV9h0i0e5ObmIkdOlEwmcfHiRRyO+lB0xyDuDTCQTERERERERERERHQ9ucEL3BtIouiOQRyO+nDx4kUkk0kzmCzfdO/kF14U3CbWSSYiIiIiIiIiIiKi60/BbSJWnEwmMTg4aJ2ZbCx1kRDRZyIiIiIiIiIiIiK6Pt3gBfoTYkWLlGAytIAyERERERERERERERGkmLFxA75kMomBgQHE43E82+7Hy7MS6msAAE0nPOg6m4ML9ptJcYNXTAcvmcIgPREREREREREREV1dlh/yYv30GHw+X+rMZDdNJzx4L8pAcjYuJID3ojloOuFRNxERERERERERERFdNbIKJnedzSo5SVh2REREREREREREdDXLKsLJGclDx7IjIiIiIiIiIiKiq1lWwWQiIiIiIiIiIiIiuj4xmExEREREREREREREaTGYTERERERERERERERpMZhMRERERERERERERGkxmEw0WiRiaH91OcKPlaKkeDO61O101YvtqkRJcSnmPrEcdR/E1c1EqaKNWFJcipKURyWao2rioehF86JSLNnV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ERERERERERERjRYMJhMRERERERERERFRWlkFk2/gKhdDxrIjIiIiIiIiIiKiq1lWweSC25LqU5Qhlh0RERERERERERFdzbIKJpdMGcS9gSRn2WbhBi9wbyCJkimD6iYiIiIiIiIiIiKiq4anv79/EACSySQGBgYQj8fxbLsfL89KqGmJiIiIiIiIiIiI6Dqy/JAX66fH4PP5Umcmezwe9SkiIiIiIiIiIiIiuk7pMWNLMNnj8SAnJwe5XuACJyYTERERERERERERXbcuJIBcL5CTkyNix/oGj8djBJMn35pA19mUSctEREREREREREREdJ3oOitixSnBZEBEmMeMGYMHAnG0fOLBe9EczlAmIiIiIiIiIiIiuo5cSADvRXPQ8okHDwTiGDNmjAgo6zfgg3YTvkQigf7+fvR86cHhqA8nv/CinwFlIiIiIiIiIiIioutCrheYfGsCDwTiyP/2IHJzc+H1eq3B5MHBQQwODiKRSODixYtIJBJIJBJIJpPGdiIiIiIiIiIiIiK69hg32svJgdfrhdfrxZgxY0Qg2eOxBpMhBZQHBweRTCaNv4mIiIiIiIiIiIjo2iffX0//2+Px4P8D+retECtDVJ8AAAAASUVORK5CYII=" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check Subscriptions\n", + "\n", + "The GET method may be used to request information about all subscriptions for this requestor. Upon success, the response contains message content with all the subscriptions for the requestor.\n", + "\n", + "![image-3.png](attachment:image-3.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecAppSupportApi.yaml#/appSubscriptions/ApplicationsSubscriptions_GET)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {}\n", + "method = \"GET\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + "print(\"Status Code\", response.status_code) \n" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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T2kxrrBwfSoiIiIiIiIiIiIyUCkthoiIiIiIiIiIiIhcMjEHlwc7YltkTALUdyIiIiIiIiIiInK9iDm4/EDKWPsmiZL6TkRERERERERERK4XMQeXZ92excO33KVZuDEY7HDy8C13Mev2LHuRiIiIiIiIiIiIyDUp5gX9REREREREREREROTGEdOCfnFxcfZNIiIiIiIiIiIiInKDChczDgkux8XFER8fjzM+gXNej71YRERERERERERERG4Q57wenPEJxMfHhwSYg4LLcXFx/uDyvUkj+fDUl9ZiEREREREREREREbmBfHjqS+5NGukPLlsDzCEzl+Pj40lISOCRlPFsPPIx247v1wxmERERERERERERkRvIOa+Hbcf3s/HIxzySMp6EBGP2slXQgn6Yi/p5vV56enr48uxRfn9qL388cxhP73lrNRERERERERERERG5TjnjE7g3aSSPpIxn7E3DSExMxOFwBAWYQ4LLFy5c4MKFC3i9Xs6fP4/X68Xr9dLb2+svFxEREREREREREZHrjy/tRXx8PA6HA4fDQUJCAg6HIyQtRkhwGUuA+cKFC/T29vr/LiIiIiIiIiIiIiLXP+v6fL6/2xf0Cxtc9vEFlBVYFhEREREREREREbmx+ILJ9qCyT5/BZRERERERERERERGRcIKX9xMRERERERERERERiYKCyyIiIiIiIiIiIiISMwWXRURERERERERERCRm/x86EXQfaRQDAwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subscribe to AppTermination Notification\n", + "The POST method may be used to create a new subscription. One example use case is to create a new subscription to the MEC service availability notifications. Upon success, the response contains entity body describing the created subscription.\n", + "\n", + "![image.png](attachment:image.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecAppSupportApi.yaml#/appSubscriptions/ApplicationsSubscriptions_POST)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {\n", + " \"subscriptionType\": \"AppTerminationNotificationSubscription\",\n", + " \"callbackReference\": \"http://my.callback.com/mec_service_mgmt_ser_availabilities/terminate\", \n", + " \"appInstanceId\": appInstanceId\n", + "}\n", + "method = \"POST\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZMAAAAvCAYAAACWulRZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACESSURBVHhe7d19cFRlni/wb6chsvbooHeq9zppKtCWyNadtUazU4CG5o6yOpt1QkQJ8bY1kp250WWBnQACorAlccC4EBS4jJMazAxFjyFASBg2I17QIgaTlJvBq5SXQQ2k6BR3urwSvbYLCd25fzzn5TlPn3O6O4QQ4Pup6iL0efr0c5637v6d5zzH09/fPwgiIiIiIiIiIiIiIhc56hNERERERERERERERCqP08zkwUHxtP4vEREREREREREREV0fPB6P5V/YBZMHBweNRzKZNP4mIiIiIiIiIiIiomufx+OBx+NBTk6O8bfH47EGk/XAcSKRwMWLF5FIJJBIJJBMJo3tRERERERERERERHTt0Wch5+TkwOv1wuv1YsyYMfB6vanB5GQyiUQigf7+fvR86cHhqA8nv/CiPyHvkoiIiIiIiIiIiIiuVbleYPKtCTwQiCP/24PIzc0VAWU9mKwva9Hf349T5wbxyw9vRtEdgyi4LYkbvOruiIiIiIiIiIiIiOhadCEBdJ3NQcsnHvzjXV9h0i0e5ObmIkdOlEwmcfHiRRyO+lB0xyDuDTCQTERERERERERERHQ9ucEL3BtIouiOQRyO+nDx4kUkk0kzmCzfdO/kF14U3CbWSSYiIiIiIiIiIiKi60/BbSJWnEwmMTg4aJ2ZbCx1kRDRZyIiIiIiIiIiIiK6Pt3gBfoTYkWLlGAytIAyERERERERERERERGkmLFxA75kMomBgQHE43E82+7Hy7MS6msAAE0nPOg6m4ML9ptJcYNXTAcvmcIgPREREREREREREV1dlh/yYv30GHw+X+rMZDdNJzx4L8pAcjYuJID3ojloOuFRNxERERERERERERFdNbIKJnedzSo5SVh2REREREREREREdDXLKsLJGclDx7IjIiIiIiIiIiKiq1lWwWQiIiIiIiIiIiIiuj4xmExEREREREREREREaTGYTERERERERERERERpMZhMRERERERERERERGkxmEw0WiRiaH91OcKPlaKkeDO61O101YvtqkRJcSnmPrEcdR/E1c1EqaKNWFJcipKURyWao2rioehF86JSLNnV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ERERERERERERjRYMJhMRERERERERERFRWlkFk2/gKhdDxrIjIiIiIiIiIiKiq1lWweSC25LqU5Qhlh0RERERERERERFdzbIKJpdMGcS9gSRn2WbhBi9wbyCJkimD6iYiIiIiIiIiIiKiq4anv79/EACSySQGBgYQj8fxbLsfL89KqGmJiIiIiIiIiIiI6Dqy/JAX66fH4PP5Umcmezwe9SkiIiIiIiIiIiIiuk7pMWNLMNnj8SAnJwe5XuACJyYTERERERERERERXbcuJIBcL5CTkyNix/oGj8djBJMn35pA19mUSctEREREREREREREdJ3oOitixSnBZEBEmMeMGYMHAnG0fOLBe9EczlAmIiIiIiIiIiIiuo5cSADvRXPQ8okHDwTiGDNmjAgo6zfgg3YTvkQigf7+fvR86cHhqA8nv/CinwFlIiIiIiIiIiIioutCrheYfGsCDwTiyP/2IHJzc+H1eq3B5MHBQQwODiKRSODixYtIJBJIJBJIJpPGdiIiIiIiIiIiIiK69hg32svJgdfrhdfrxZgxY0Qg2eOxBpMhBZQHBweRTCaNv4mIiIiIiIiIiIjo2iffX0//2+Px4P8D+retECtDVJ8AAAAASUVORK5CYII=" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check Subscriptions\n", + "\n", + "The GET method may be used to request information about all subscriptions for this requestor. Upon success, the response contains message content with all the subscriptions for the requestor.\n", + "\n", + "![image-3.png](attachment:image-3.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecAppSupportApi.yaml#/appSubscriptions/ApplicationsSubscriptions_GET)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {}\n", + "method = \"GET\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + "print(\"Status Code\", response.status_code) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscriptions_list = response.json()\n", + "pd.DataFrame.from_dict(subscriptions_list['_links']['subscriptions']).reindex(columns=[\"subscriptionType\"]).style" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "## (3) Interacting with the Location API (MEC013)\n", + "\n", + "https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml\n", + "\n", + "For this part of the tutorial, we will use the MEC Location (MEC013) API ```location```, version 3:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mec013_path = mec_base_path + \"/location/v3\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check if the Location Services are available in the choosen MEP \n", + "For this part of the tutorial, we will use theWe will need to use the MEC Service Management (MEC011) API ```mec_service_mgmt```, version 1:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mec011_service_mgmt_path = mec_base_path + \"/mec_service_mgmt/v1\"" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following method retrieves information about a list of mecService resources. This method is typically used in \"service availability query\" procedure\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs011-app-enablement-api/-/raw/master/MecServiceMgmtApi.yaml#/services/Services_GET)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_service_mgmt_path + \"/services\"\n", + "payload_dic = {}\n", + "method = \"GET\" # <-- GET, POST, DELETE\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "| ![image.png](attachment:image.png) | Look for the mec013 service in the previous replay. If it shows that the service mec013 is **listed** and is **active**, you can proceed to the next step |\n", + "| --------------|-------------- |\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "services_list = response.json()\n", + "pd.DataFrame.from_dict(services_list).reindex(columns=[\"serName\", \"serCategory\", \"version\",\"state\"]).style" + ] + }, + { + "attachments": { + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZoAAAAuCAYAAACh86w2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABfASURBVHhe7d1/bBzlncfx9+7GJpflV6OeK/AiEyMo7XHoqK9yAmZzd6RQUkhcII5PiwqmOnPkICI/SCAQTm1oIGl+0JBLW5/AVdRtHacJjqFpqRJQFoNjVT56gFAuFAeLtXKsEDhclyZr7/r+mJndmdnxrr0JqRN/XtIqYWb2mWeenVmUzz7zHV8qlRpBRERERERERERERKREvrEEzSMjxibWnyIiIiIiIiIiIiIyOfh8PsefXgoGzSMjI9lXJpPJ/l1EREREREREREREzn4+nw+fz4ff78/+3StwHjVotkLldDrN8PAw6XSadDpNJpPJrhcRERERERERERGRs48VJvv9fgKBAIFAgClTphAIBDzD5lGD5kwmQzqdJpVK0X/Mx/54kMMfB0il3VuKiIiIiIiIiIiIyNmoPABXTE9zQyhJ1QUjlJeXEwgE8Pv9ju08g2arVEYqleLIJyP8+M3zmXv5CDUXZTgn4N5aRERERERERERERM5GJ9LQe9TP3nd93Hf1p8z4go/y8vJsKQ2LM3a2yWQyDA8Psz8eZO7lI1wbUsgsIiIiIiIiIiIiMpmcE4BrQxnmXj7C/niQ4eHhbHllO8+g2f4AwMMfB6i5KP+NIiIiIiIiIiIiIjI51FxkZMWZTCabH9t5Bs3Yy2ekjdRaRERERERERERERCancwKQShuVMNwhM4WCZsywWURERERERERERESEApmx58MAM5kMQ0NDJJNJHumuYP2ctHsTADoO+eg96ueE92pxOSdgTDGvvzJvyEVEREREREREREQmtBX7Ajw5K0EwGKSsrAy/PzePueCM5kI6Dvl4Pa6QeTxOpOH1uJ+OQ7mnMYqIiIiIiIiIiIic6UoOmnuPlvzWSU9jJyIiIiIiIiIiImeTkhNPzWQuncZOREREREREREREziYlB80iIiIiIiIiIiIiIihoFhEREREREREREZGTpaBZRERERERERERERE6KgmYREREREREREREROSkKmkXONPHdLJ3XQH3eawl74u6NSzHAngcaWLpjwL3iNBtPP3rYZI7D2Lb/vI2n76fC6d6fTFS9Gxqo39Bj/pft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He695SqCp+lyEpGJo3dDA2vIfb+KiIiIiIiITHQr9gV4claCYDBIWVkZfn8ub534QTMAaYaOfWiUNsj6K86Z/gVyvThO6uOPGbZvYpcNpHNGjif482fD+Kd9ialTT+fxjCNofutZ7n70JYZufJzo/WOcYesVhJ51Btn3cDNb37mC5ueeYO4X3etFRCY2Bc0iIiIiIiJypikUNE/s0hlZAcoucNddtofMAFMpz6vNbHu5QmYA39QKpk2/+LSHzGM3RHfHSwxyBZHGsYbMk0Tfb2l/B4I3NipkFhERERERERER+Qs7Q4LmyaqMWavb6ejUjN081Y20dLaPY5a3iMjEUrO8XbOZRURERERE5KyhoPmsVMn8Z9rp6Dxby2aIiIiIiIiIiIjIRKKgWUREREREREREREROioJmERERERERERERETkpJQfN50zU5+edATR2IiIiIiIiIiIicjYpOWiuuSjjXiRjpLETERERERERERGRs0nJQXP9lSNcG8podu44nBOAa0MZ6q8cca8SEREREREREREROWP5UqlUXuqZyWQYGhoimUzySHcF6+ek3ZuIiIiIiIiIiIiIyCSyYl+AJ2clCAaDlJWV4ffn5jEXnNHs8/nci0RERERERERERERkkhotMx41aPb5fPj9fsoDcEITmkVEREREREREREQmrRNpKA+A3+/3DJs9g2afz5cNmq+Ynqb3qOdmIiIiIiIiIiIiIjIJ9B41smIraHaHzaMmyH6/nylTpnBDKMned328HvdrZrOIiIiIiIiIiIjIJHIiDa/H/ex918cNoSRTpkxx1Ga2eD4MEPOBgOl0mlQqRf8xH/vjQQ5/HCClsFlERERERERERERkUigPwBXT09wQSlJ1wQjl5eUEAoG8sHnUoHlkZISRkRHS6TTDw8Ok02nS6TSZTCa7XkRERERERERERETOPlZpDL/fTyAQIBAIMGXKFAKBgGfpjFGDZmxh88jICJlMJvt3ERERERERERERETn72Z/nZ/3dHTID/D9ZiqGq4Ms+3gAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### List of zones \n", + "\n", + "To pick a zone of interest (e.g. zone with the most terminals) lets first check the list the avaliable ones usin the Location APIs. This query retrieves information about one or more specific zones or a list of zones. The output is a list of identifiers for zones authorized for use by the application. \n", + "\n", + "![image-3.png](attachment:image-3.png)[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml#/location/zonesGET)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec013_path + \"/queries/zones\"\n", + "payload_dic = {}\n", + "method = \"GET\"\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zone_list = response.json()['zoneList']['zone']\n", + "pd.DataFrame.from_dict(zone_list).reindex(columns=[\"zoneId\", \"numberOfUsers\", \"numberOfAccessPoints\", \"numberOfUnserviceableAccessPoints\"]).style" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZMAAAAwCAYAAABkOuQXAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABmbSURBVHhe7d19cBPnnQfwrywwHEqahOmpQyXGwQyE9trMUU/HJnXEXUNDS1Pj8GLcUdrEvZ7bMMDFhgChgZuW1mBqm9bhSI454rsMao0Jju2kXLkBMhZKbE9GlxzJZDho7GiQhosmk5hMlRrJku6PfdGzq9WbbRzZ/n5mNLb3Tc+z+zwr70/P/tYUDofjICIiIiIiIiIiIiJKw5QqmByPS5OVn0REREREREREREQ0M5hMJs1PGAWT4/G4+orFYurvRERERERERERERDT9mUwmmEwmFBQUqL+bTCZtMFkJHEejUYyOjiIajSIajSIWi6nziYiIiIiIiIiIiGj6UUYhFxQUwGw2w2w2Y9asWTCbzcnB5Fgshmg0inA4DN8NE877LbjykRnhqLhJIiIiIiIiIiIiIpquCs3A0vlRPGAPoeiOOAoLC6WAshJMVtJahMNhDH0cx7OXPoc1S+IoWRDDHLN+c0REREREREREREQ0Hd2MAt7rBThz1YTH7/0Ei+4yobCwEAXiQrFYDKOjozjvt2DNkjjuszOQTERERERERERERDSTzDED99ljWLMkjvN+C0ZHRxGLxRLBZPGhe1c+MqNkgZQnmYiIiIiIiIiIiIhmnpIFUqw4FoshHo9rRyarqS6iUvSZiIiIiIiIiIiIiGamOWYgHJUyWiQFkyEHlImIiIiIiIiIiIiIIMSM1QfwxWIxRCIRhEIhPNVnxaFVUf06AICuyyZ4rxfgpvFs0pljloaDVy5jkJ6IiIiIiIiIiIimlp3nzDiwIgiLxZI8MjmdrssmvO5nIDkXN6PA6/4CdF026WcRERERERERERERTRk5BZO913NanATcd0RERERERERERDSV5RTh5IjkseO+IyIiIiIiIiIioqksp2AyEREREREREREREc1MDCYTERERERERERERUUYMJhMRERERERERERFRRgwmExEREREREREREVFGDCYTTQX+TtRXVKEy6VWHbr9+4bEIoHtrFepPBvQzJtkkl6O/FZUVrfDqp4+Dt6kKlU0D+smGgifr5OM4sWUYq1zKnj3pmFZW3Ipt30K6tpE4VgfxL2No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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get information about the location of UEs\n", + "The GET method is used to query location information about UEs (User Equipments) .\n", + "\n", + "![image-2.png](attachment:image-2.png)[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml#/location/userSubListGET)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec013_path + \"/queries/users\"\n", + "payload_dic = {}\n", + "method = \"GET\" \n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "user_list = response.json()['userList']['user']\n", + "pd.DataFrame(user_list, columns=[ \"address\", \"zoneId\", \"accessPointId\", \"locationInfo\", \"timestamp\"]).style " + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB44AAAA+CAYAAADUDDb+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAC3CSURBVHhe7d1/VNT3ne/xJ44YhUSgBbwlKAmLUevFJuJmx96ge0NyxXYhRtlEemyIpkRrbCTXwtbgaTAn6C6sJ2iNgbCRkHqCpmpSuEbYlpwKnjprgyayibqxJCChq9iCJoOWcfT+8Z1fzAwwoCYaX49z5gS/8/kOn/l+vt8hZ17f9+cT1Nvbe5krcPnyFe0uIiIiIiIiIiIiIiIiIiLXQFBQkPemfgUNJzj2DIsVHIuIiIiIiIiIiIiIiIiIXH88g+PBQuQhBcfOkPjy5cuuh+d2ERERERERERERERERERH56jmD4qCgINfDc7u3gINjz7D40qVLXLp0qc82hcciIiIiIiIiIiIiIiIiIl89z7A4KCiIESNGMGLECJ8Quc8+gQTHnoGx3W53PbwDZBERERERERERERERERER+Wp5B8Ymk8n18AyQ++wzWHDsHRpfvHiRE+c7qfv8GM09HZyzX/DeRUREREREREREREREREREvmJjTaNJDIlhzm2TSRgTxciRI/sNjwMKji9dusTFixe5ePEiv/v8Y147c5AFUdP527A7iRg5xnsXERERERERERERERERERH5inVdPM8fzn7Crs5DPB55L39/20RGjhzJyJEjXeGx04DBsbPa2G63Y7PZ+LjnNC/897/z7B1ziRv9Te/mIiIiIiIiIiIiIiIiIiJynWm98GfWfbqXNf/j/zAxJJrg4GBMJlOfquMR3jt585ymuu7zYyyImq7QWERERERERERERERERETkBhE3+pssiJpO3efHsNvtXLp0icuX+9YXBxQcO8Pj5p4O/jbsTu8mIiIiIiIiIiIiIiIiIiJyHfvbsDtp7ulwhcYBB8eejZ3B8Tn7Ba1pLCIiIiIiIiIiIiIiIiJyg4kYOYZz9gt9qo09M+F+g2MnZ2PvxFlERERERERERERERERERG4s/eW/gwbHeCXNIiIiIiIiIiIiIiIiIiJyY+ov+w3q7e313eqxg91up7e3lwsXLrDksx1UfPtx76YuJy90UX3mfZq/+Iy/Xrro/bR8hW4ZMZLEW28nPfJuxo+O8H5aRERERERERERERERERL7mFn/0Gltvf5TRo0czatQoTCYTQUFBBAUFBVZxHIiTF7pY/+k7vHeuVaHxdeivly7y3rlW1n/6DicvdHk/LSIiIiIiIiIiIiIiIiI3sasWHFefeZ/zl2zem+U6c/6Sjeoz73tvFhEREREREREREREREZGb2FULjpu/+Mx7k1ynNFYiIiIiIiIiIiIiIiIi4umqBceanvrGobESEREREREREREREREREU9XLTgWEREREREREREREREREZEbk4JjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbXFBvb+9l740Aly9f5vLly9jtdnp7e7lw4QJLPttBxbcf924KwOKPXvPedO2Nm8+vYscT6r3dw8k/b2Tpp95bDfPvWsaPbrvFa+tf+aC9lNWnvDZ7WDl1JXNG991m/byRf/yvQ303BtA/J7/7X0P9jeMVsTZQnFmBxQ6TsktYNy/Mu8U10ETJ9zfTCBA3n5e3pBHt3eRGYW2i/Cdl1J6C2LRnWLdsSkDnzrXSvquQZytPYB07heWFeaTEebe4QdjbqMp+jp2nIHROHq8/PcW7hQym6yi1lbvZ+x+ttJ+zAcGEjo8jJfNJfjA7imDv9teYrcPCa5VneWj1nBv3eh+i01X5/HgbZJUWkj7e+9kvyYEyFrxgIXlNBTkzvZ8cQNdRqjd8QOwLC5kOQAfVy/Op5Ev8zLaf5fibZRz6mzwy7/V+UgLmcxz9jKW9g/q1JVS+34nVDrGZP+DOqjdonL2CXXlJ3q94ZXz6c51cKyIiIiIiIiIiItexxR+9xtbbH2X06NGMGjUKk8lEUFAQQUFBN3jF8aVL+E29PYz/5kp+dZfxVbXbA5QlrfQTGgPcwndiV/LO3fOZ7/3UuPn8Ksk3NAYIvS3Zd58A+ud02R5oy+tXy87/h8UOmKaRPufLCI2/Xk5X76b2lA2w0V7TwHHvBleg+3ANlesLWZn5HNUnvZ/1p4mdW09gtTtCw993eDe4Ydj27WLnKYAo5v6DQuOhsu4v44msIsr39RCbuoDcvKXkZqeSSCvVRXksK2rC6r3TNda8rYzak73em+U6dbp2G5WHz3pv/nJ1NPBv245yyu79hAxJIMfxwG62NHUSMXsh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MUAMc5/PN/riuV7uqBBip84KvBJYRERERERERERERETE4cauOJZh628cB2fDsvZJig8SWFWnXFX9VrEKLZV55L7ZCaZp5O54JsB1t0VkuFRxLCIiIiIiIiIiInLj+fpWHMuX78y7VB80fjQvSlNoLNcH+1Hq93QCEL1ggUJjERERERERERERERGRIVJwLEMTOYd1eyrYtaeC3PsHm6Ra5EtimkL2m8Z5+XLWBO9nReQaiM4sZNceVRuLiIiIiIiIiIiIfF0oOBYRERERERERERERERERuckpOBa5gUzPM6pqVeUnIiIiIiIiIiIiIiIiV5OCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm9xVC45vGTHSe5NcpzRWIiIiIiIiIiIiIiIiIuLpqgXHibfe7r1JrlMaKxERERERERERERERERHxdNWC4/TIuxkzIth7s1xnxowIJj3ybu/NIiIiIiIiIiIiIiIiInITu2rB8fjREay+43vMGBunqZCvQ7eMGMmMsXGsvuN7jB8d4f20iIiIiIiIiIiIiIiIiNzEgnp7ey97bwS4fPkyly9fxm6309vby4ULF1jy2Q4qvv24d1MREREREREREREREREREbnOLf7oNbbe/iijR49m1KhRmEwmgoKCCAoKCqzi2NlYRERERERERERERERERERuXP1lv4MGx84d/e0sIiIiIiIiIiIiIiIiIiI3jv7y336DY8/GQUFBjBgxgrGm0XRdPO/dVERERERERERERERERERErmNdF88z1jSaESNG9MmBnT/3Gxw7ORuPGDGCxJAY/nD2E+8mIiIiIiIiIiIiIiIiIiJyHfvD2U9IDIlxBccBVxw7OUNjk8nEnNsms6vzEK0X/uzdTERERERERERERERERERErkOtF/7Mrs5DzLltMiaTqU/VsVNQb2/v5T5bvFy+fJlLly5x8eJFLl68yO8+/5jXzhxkQdR0/jbsTiJGjvHeRUREREREREREREREREREvmJdF8/zh7OfsKvzEI9H3svf3zaRkSNHMnLkSJ/wOKDg2Bke2+12Ll68yInzndR9fozmng7O2S947yIiIiIiIiIiIiIiIiIiIl+xsabRJIbEMOe2ySSMiWLkyJF9Ko6HFBzjJzx2Pi5dusSlS5dcz4uIiIiIiIiIiIiIiIiIyFfLGQqPGDHCtSyx8+EvNCbQ4BiP8NgZIHsGxgqORURERERERERERERERESuD85g2DtA9tzmLeDgGEd47PyvZ1is0FhERERERERERERERERE5PrhDIe9w2J/oTHA/wf0qsMboSHotwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subscribe zoneLocationEventSubscription (notifies every time there is a zone change)\n", + "\n", + "Subscribe to the service that notifies everytime there is change in a zone.\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "[swagger](https://forge.etsi.org/swagger/ui/?url=https://forge.etsi.org/rep/mec/gs013-location-api/raw/master/LocationAPI.yaml#/location/zoneSubPOST)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec013_path + \"/subscriptions/zones\"\n", + "payload_dic = {}\n", + "method = \"POST\"\n", + "\n", + "payload_dic ={\n", + " \"zoneLocationEventSubscription\": {\n", + " \"subscriptionType\": \"ZoneLocationEventSubscription\",\n", + " # URI exposed by the client on which to receive notifications via HTTP\n", + " \"callbackReference\": \"http://my.callback.com/zone-location-event-notification/zone3\", \n", + " # Identifier of zone (e.g. zone001) to monitor.\n", + " \"zoneId\": \"zone03\",\n", + " # A correlator that the client can use to tag this particular resource representation during a request to create a resource on the server. \n", + " \"clientCorrelator\": \"0123\",\n", + " \"requestTestNotification\": True,\n", + " # List of the users to be monitored. If not present, all the users need to be monitored.\n", + " \"addressList\": [\n", + " \"10.1.0.1\",\n", + " \"10.100.0.1\",\n", + " \"10.10.0.1\",\n", + " \"10.100.0.2\",\n", + " \"10.100.0.3\"\n", + " ],\n", + " \"reportingCtrl\": {\n", + " # Maximum number of notifications. Default is 0 (no max value)\n", + " \"maximumCount\": 150,\n", + " # Maximum frequency (in seconds) of notifications per subscription.\n", + " \"maximumFrequency\": 0,\n", + " # Minimum interval between reports in case frequently reporting. Unit is second.\n", + " \"minimumInterval\": 5\n", + " },\n", + " # List of user event values to generate notifications for.\n", + " \"locationEventCriteria\": [\n", + " \"ENTERING_AREA_EVENT\", \"LEAVING_AREA_EVENT\"\n", + " ],\n", + " \"expiryDeadline\": {\n", + " \"seconds\": 1977836800,\n", + " \"nanoseconds\": 0\n", + " }\n", + " }\n", + "}\n", + "\n", + "response = requests.request(\n", + " method, \n", + " url, \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# check subscription \n", + "# GET /subscriptions/zones/{subscriptionId}\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB48AAABACAYAAAAK5jFxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACxASURBVHhe7d1/VNT3ne/xJ44QZRLBFvSG4I9ysWq92Ebc7NgbdG9JF2wKTdVG6bEhmhKtsUquha0hp2KO6F5YT9AmBmQjIeUETcSksEbYhJwKnjq1QVNpoq6WKBKyCi1gMmoZR+4f83sYYPBHosnrcc6cwHc+3+Ez3+/nO/F8X/P+fIJ6enp6ERERERERERERERERERGRL7Wgaw2Pe3uvaTcREREREREREREREREREbmJgoKCfDcFZEjhsWdgrPBYREREREREREREREREROTW4xkeDyVIDig8dgbFvb29rofndhERERERERERERERERER+fw5w+KgoCDXw3P7QAYNjz0D46tXr3L16lWvbQqQRUREREREREREREREREQ+f56BcVBQEMOGDWPYsGF9guT+DBgee4bGNpvN9fANkUVERERERERERERERERE5PPlGxobDAbXwzNE7k+/4bFvcHzlyhVOXWqn9pPjNF1s44Ltsu8uIiIiIiIiIiIiIiIiIiLyORtlGEFcaBRJd00hdmQkw4cPDyhAHjA8vnr1KleuXOHKlSv87pOTvNRxiPmRM/iHsK8xevhI311ERERERERERERERERERORz1nnlEn/s/pDK9sM8GnEf/3TXJIYPH87w4cNdAbI/fsNjZ9WxzWbDarVy8uJ5Nvz3f/LUxLlMGPFV3+YiIiIiIiIiIiIiIiIiInKLOXP5r2w8vY+n/8c/Myl0DMHBwRgMhn6rj4f5bnDynLK69pPjzI+coeBYREREREREREREREREROQ2MWHEV5kfOYPaT45js9m4evUqvb19aotdBgyPnQFy08U2/iHsa75NRERERERERERERERERETkFvYPYV+j6WKbKzgeUnjsuYMzPL5gu6w1jkVEREREREREREREREREbjOjh4/kgu2yV9VxfyFyn/DYybmDv51EREREREREREREREREROT2EUj+2294zACJs4iIiIiIiIiIiIiIiIiI3D4CyX6Denp6vFo4d7LZbPT09HD58mWWfrSL0m886tnMy9nLnVR1vEfTpx/x96tXfJ+Wz9Edw4YTd+c9pEZ8i3EjRvs+LSIiIiIiIiIiIiIiIiJfcEs+eIkd9yxkxIgRhISEYDAYCAoKIigoyKvdgJXHgTh7uZNNp9/k3QtnFBzfgv5+9QrvXjjDptNvcvZyp+/TIiIiIiIiIiIiIiIiIiJwI8Ljqo73uHTV6rtZbjGXrlqp6njPd7OIiIiIiIiIiIiIiIiICNyI8Ljp0498N8ktSudKRERERERERERERERERPpz3eGxpqq+fehciYiIiIiIiIiIiIiIiEh/rjs8FhERERERERERERERERGR25/CYxERERERERERERERERERUXgsIiIiIiIiIiIiIiIiIiIKj0VEREREREREREREREREROGxiIiIiIiIiIiIiIiIiIig8FhERERERERERERERERERACCenp6ej039Pb20tvbi81mo6enh8uXL7P0o12UfuNRz2YuSz54yXfTzTd2Hq9Fj8Pou93D2b9uYdlp3612876+nJ/edYfP1r/zp9Yi1p7z2exh9bTVJI3w3mb5pIEf/ddh740B9M/J7/43UX/n8bpY6ilIK8Vsg8kZhWx8KMy3xU3QSOGDz9EAMGEeL2xLYYxvk9uFpZGSnxdTcw6iU55k4/KpAY2dm6W1Mo+nyk5hGTWVFXnZJE7wbXGbsLVQkbGO3efAmJTNy6um+rb48ulpx1xRTtVbxzjRaQXAOHo8MxYsIiNlKkaD7w43Wecxqjb/iegNi5jh+9wtzfH5M2clldnxvk9+RtqoWpFDGUP9/LNyfn85r3QmkflQlH3TwWLmbzCT8HQpmbN8298c1jYzL5V184O1SUPou+D32PUdj5Yj5eTl13PighUM8WRlBVPwrzfnHAfSHxEREREREREREfn8LfngJXbcs5ARI0YQEhKCwWAgKCiIoKAgr3a3Z+Xx1at4Jd5+jPvqal77um8c8QDF8av9BMcAd/DN6NW8+a15zPN9auw8XovvGxwDGO9K6LtPAP1z6rUF2vLW1bz7PzDbAMN0UpM+i+D4i+V81R5qzlkBK63V9ZzwbXAduo5UU7Ypj9Vp66g66/usP43s3nEKi80e7NX8vs23wW3Dur+S3ecAIpn7fQXH1uZqnlqYTUHlGUJmppCZvYysVfMwje2moSSfRzKrabX57nVzna8pp+xIt+9muamO8kp+PR9d8t3+2WoqL6bmbI/vZgnA4MeujX3FdZwglgWrlpG1Jpkpw33b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bsRo36dFRERERERERERERERERAAI6unp6fXc0NvbS29vLzabDavVyuXLl1nSupPSbzzq2UxERERERERERERERERERG4DSz54idLoRYwYMYLg4GAMBgNBQUEEBQV5teu38tjZ2HcHERERERERERERERERERG5vQSS//YJjz13CAoKYtiwYYwyjKDzyiXfpiIiIiIiIiIiIiIiIiIicgvrvHKJUYYRDBs2zCsH9hci9wmPnZw7DBs2jLjQKP7Y/aFvExERERERERERERERERERuYX9sftD4kKjXOGxv9DYacDweNiwYRgMBpLumkJl+2HOXP6rbzMREREREREREREREREREbkFnbn8VyrbD5N01xQMBoNX9bE/QT09Pb2+GwF6e3u5evUqV65c4cqVK/zuk5O81HGI+ZEz+IewrzF6+EjfXURERERERERERERERERE5HPWeeUSf+z+kMr2wzwacR//dNckhg8fzvDhwwcMkAcMj50Bss1m48qVK5y61E7tJ8dputjGBdtl311ERERERERERERERERERORzNsowgrjQKJLumkLsyEiGDx/uVXk85PAYPwGy83H16lWuXr3qel5ERERERERERERERERERD5fzmB42LBhriWKnY/BgmMGC4/xCJCdIbJnaKzwWERERERERERERERERETk1uAMh31DZM9tAxk0PMYRIDv/6xkYKzgWEREREREREREREREREbl1OANi38B4sOCYQMNjJ8+wWMGxiIiIiIiIiIiIiIiIiMitxzMoDiQ0dhpSeOxJ4bGIiIiIiIiIiIiIiIiIyK1nKIGxp/8P7IpKeB2+xjMAAAAASUVORK5CYII=" + }, + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB44AAABDCAYAAABjsCjhAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAADEKSURBVHhe7d1/VNTXve//J4xjIpMKpoI3BNFytGpzsQnkpGNv0HVD8hWbA8coJ5EuK1FLtGoiWTlwr8FvxNyg58LXFbRqIDQiKSuYFI2FGvEmZFVwHadp0FSaoNWSiIRcgRawGUwYR79/zAwMwwADYuKP12OtycLP7M+wZ+/9+ZC135/33n5dXV1XEBERERERERERERERERGRW5a/5wEREREREREREREREREREbm1KHAsIiIiIiIiIiIiIiIiInKLU+BYREREREREREREREREROQWp8CxiIiIiIiIiIiIiIiIiMgtzq+rq+uK58GhuHLlqk4XEREREREREREREREREZFvgJ+fn+ehbsMOHF+5cqX7dfny5e5jIiIiIiIiIiIiIiIiIiJyfXAFi/39/fHz8+t+eRpW4NgVLLbb7Vy6dAm73c7ly5e7A8kiIiIiIiIiIiIiIiIiIvLtcgWJ/f39MRgMjBo1CoPB0B1E7lV2qIHjK1euYLfbsdlsnO5spuJCHbWdTVywf+VZVEREREREREREREREREREvmVjDbcTGRBK3NgZTA0IwWg0YjAYegWPhxQ4dmUa22w23u84RWHLH1gYHMU/B36PcaPGeBYXEREREREREREREREREZFvWduli/yx41P2thxjafCPeChwGkajsVfm8ZACx5cvX+bSpUuc/McX/K8vDvH85HlMuv27nsVEREREREREREREREREROQ6c/arv7Hps4P8v3fNZfp37mLUqFH4+/sD4PivD1z7F1+6dImKC3UsDI5S0FhERERERERERERERERE5AYx6fbvsjA4iooLdVy6dKk7BsxQAsc4M47tdju1nU38c+D3PN8WEREREREREREREREREZHr2D8Hfo/azibsdjuXL1/uPj6kwDHO4PEF+1fa01hERERERERERERERERE5AYzbtQYLti/6hU0ZqiBY/dUZRERERERERERERERERERuTF5xn6HFDjG+QEiIiIiIiIiIiIiIiIiInLj8oz7+nV1dfkUCb5y5Qo2mw2r1cqT50oo/MGTnkW6nfuqjbLWj6j98nO+vnzJ8235Ft3mP4rI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ilm+rhLrxGh++tj9hNzewamyg5Sd7NCKDSIiIiIiIiIiMqJu3oxj+ea1vk/ZB44fzYvjFTSW64O9jsoDjmWSQxYuVNBYRERuLDMXs23zAqJx7vWbvYfKC8EkuO+9LiIiIiIiIiIico0o4/gW118/yvVNGcciIiIiIiIiIiIiIiIyXMo4FhERERERERERERERERGRPhQ4FrkBRaUXsvdAIXsPKNtYRERERERERERERERErp4CxyIiIiIiIiIiIiIiIiIitzgFjkVEREREREREREREREREbnEjHji+zX+U5yG5TqmvRERERERERERERERERIRrETiOvONuz0NynVJfiYiIiIiIiIiIiIiIiAjXInCcMP5exvgbPQ/LdWaMv5GE8fd6HhYRERERERERERERERGRW9CIB44n3j6OdZN/wv1jJ2kp5OvQbf6juH/sJNZN/gkTbx/n+baIiIiIiIiIiIiIiIiI3IL8urq6rnge9ObKlSvYbDasVitPniuh8AdPehYREREREREREREREREREZHr3NJPdrN7YhImkwmj0Yifn9/QM479/Pw8D4mIiIiIiIiIiIiIiIiIyA3EM+47pMCxn59fnw8QEREREREREREREREREZEbi2fsd0iBYwB/f3/GGm6n7dJFz7dEREREREREREREREREROQ61nbpImMNt+Pv3ztUPKTAsb+/PwaDgciAUP7Y8ann2yIiIiIiIiIiIiIiIiIich37Y8enRAaEYjAYegWPfQ4cu1KVR40aRdzYGextOcbZr/7mWUxERERERERERERERERERK5DZ7/6G3tbjhE3dgajRo3qtVy1X1dX1xXPE/pz5coVLl++jM1m4/2OUxS2/IGFwVH8c+D3GDdqjGdxERERERERERERERERERH5lrVdusgfOz5lb8sxlgb/iIcCp2E0GvH39x9e4Bhn8Nhut2Oz2Tjd2UzFhTpqO5u4YP/Ks6iIiIiIiIiIiIiIiIiIiHzLxhpuJzIglLixM5gaEILRaMRgMHQHjRlO4Bi3zGO73c6lS5ew2+1cvnyZK1eucOXKkD9ORERERERERERERERERERGmGspan9/fwwGA6NGjere29g9aMxwA8c4g8eu1+XLl7uPiYiIiIiIiIiIiIiIiIjI9cEVIHYFi933Ne5VbriBYxcFi0VERERERERERERERERErn/eAsYuVx04FhERERERERERERERERGRG5u/5wEREREREREREREREREREbm1/P9coqY03yVljgAAAABJRU5ErkJggg==" + }, + "image-4.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB4cAAAA/CAYAAADjRc6RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACq8SURBVHhe7d1/VFXnne/xtxxBhcQfLeANwR/lag11tK2kKfYGnVuSAetAE/Um0uVI0KJWUyXLC1MlK2JW0BkYV9AxBmQiIeMqJlWTwpjApGRVcI3npkFTaaJWSvxByCi0/kgOWvDI/eP82mdzgAMYg/p5rbVXce9nn/Oc59n7pGt/z/f7DGlvb+9ERERERERERERERERERETuaEP6Ehzu7PS7qYiIiIiIiIiIiIiIiIiI3CJDhgwx7+qi1+CwMSCs4LCIiIiIiIiIiIiIiIiIyOBjDA53FyjuNjjsCgR3dna6N+N+ERERERERERERERERERH56rmCwUOGDHFvxv3udr6Cw8aA8I0bN7hx44bXPgWIRURERERERERERERERES+esaA8JAhQwgICCAgIKBLoBhfwWFjUNhut7s3c5BYRERERERERERERERERES+WuagsMVicW/GIDHm4LA5MHz9+nUarrZQ9fkJ6tuauWK/ZnwfEREREREREREREREREREZBEZahjMtOIKEex9g0ogwhg4d2iVA3CU4fOPGDa5fv87169f57eeneLX1feaHzeB7o77BmKEjvN9BRERERERERERERERERES+chevX+V3lz9hX8sRngp9iL+9dzJDhw5l6NChngCxKzjsyhq22+10dHRwqu0CL/z3f7J+4hwmDP+6+bVFRERERERERERERERERGSQOXPtz2w6/Q7P/o+/Y3JwOIGBgVgsFkfpaWNDY0npqs9PMD9shgLDIiIiIiIiIiIiIiIiIiK3iQnDv878sBlUfX4Cu93OjRs36Ox0FJPuEhx2BYjr25r53qhvGA+LiIiIiIiIiIiIiIiIiMgg971R36C+rdkdGPYKDht3uILDV+zXtMawiIiIiIiIiIiIiIiIiMhtZszQEVyxX/PKGu7s7PSdOexqICIiIiIiIiIiIiIiIiIitydz/NcrOGxsICIiIiIiIiIiIiIiIiIity9z7HdIe3t7p2un3W6nvb2da9euseTT1yn51lNeJxudu3aR8tYPqf/iU/5647r5sHyFhgUMZdo995Mc+h3GDR9jPiwiIiIiIiIiIiIi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JvXh8YQbM8NHjGJKTBKZxQVdswwtESQXFDg/Q7Dn844YxZSHF7IpvecA8JfFU/I8kNGTY0h9No9CX4FhHFmZ2YXLvdbsvRkik7LYVpDm/bpBwUROjSezuMATGO6XPs5VazX7fuMIxE5Jju85MAwwYjorC54mcYIrWzOQyKlJZO/ayAIfGaF9vY6xRBC/uYCXs0zXTVAwkVNnkfpsLqseMrTvkwjmPJ/NyqRJRI4xjI3ztf3O6BURERERERERERERkbve7ZM5LP3W3Tz2rgPrxmXkv49/2ZlyU/nOsBWAxtIsMt9oAct0Ml9/xs91sEXuZMocFhERERERERERERFvt3fmsNx6re9R7lwnNXZRkgLDMjjYj1N9oAWA8PnzFRgWERERERERERERERHxk4LD0r3QBDYdKGHfgRIyf9hbQWmRW8QSTfobjuvy5dTx5qMid6kIkneUsE9ZwyIiIiIiIiIiIiLSAwWHRURERERERERERERERETuAgoOiwxSM7Ic2bH7Dmi9YRERERERERERERERERk4BYdFRERERERERERERERERO4CCg6LiIiIiIiIiIiIiIiIiNwF+hUcHhYw1LxLBinNlYiIiIiIiIiIiIiIiIjQ3+DwtHvuN++SQUpzJSIiIiIiIiIiIiIiIiL0NzicHPodRgQEmnfLIDMiIJDk0O+Yd4uIiIiIiIiIiIiIiIjIXahfweFxw8ewbuKPeHDkBJUtHoSGBQzlwZETWDfxR4wbPsZ8WERERERERERERERERETuQkPa29s7Ozs76ezsxG63097ezrVr11jy6euUfOspc3sRERERERERERERERERERnk0j5+lV33P8nw4cMJCgrCYrF0zRweMmQIQ4YMMe8WEREREREREREREREREZHbiDn26xUcdh1UcFhERERERERERERERERE5PZmjv8GGHe6/g4ICGCkZTgXr1/1PltERERERERERERERERERAa1i9evMtIynICAAO84sLGRK0gcEBDAtOAIfnf5E+NhEREREREREREREREREREZ5H53+ROmBUe4g8NemcMursCwxWIh4d4H2NdyhDPX/mxsIiIiIiIiIiIiIiIiIiIig9SZa39mX8sREu59AIvF4p093N7e3ulq2NnZyY0bN7h+/TrXr1/nt5+f4tXW95kfNoPvjfoGY4aOML6uiIiIiIiIiIiIiIiIiIgMAhevX+V3lz9hX8sRngp9iL+9dzJDhw5l6NChngxic3DYFSC22+1cv36dhqstVH1+gvq2Zq7Yr3m/g4iIiIiIiIiIiIiIiIiIfOVGWoYzLTiChHsfYNKIMIYOHeqVOdwlOIyPALFru3HjBjdu3HAfFxERERERERERERERERGRr5Yr8BsQEOBeQti1mdcc7hIcxhAgdgWJjUFhBYdFRERERERERERERERERAYHd1awKUhs3Odu6ys4jDNA7PpfY0BYgWERERERERERERERERERkcHDnRlsCggbA8MA/x9LdujmLSy7GwAAAABJRU5ErkJggg==" + }, + "image-5.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAB44AAAA+CAYAAADUDDb+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAC3CSURBVHhe7d1/VNT3ne/xJ44YhUSgBbwlKAmLUevFJuJmx96ge0NyxXYhRtlEemyIpkRrbCTXwtbgaTAn6C6sJ2iNgbCRkHqCpmpSuEbYlpwKnjprgyayibqxJCChq9iCJoOWcfT+8Z1fzAwwoCYaX49z5gS/8/kOn/l+vt8hZ17f9+cT1Nvbe5krcPnyFe0uIiIiIiIiIiIiIiIiIiLXQFBQkPemfgUNJzj2DIsVHIuIiIiIiIiIiIiIiIiIXH88g+PBQuQhBcfOkPjy5cuuh+d2ERERERERERERERERERH56jmD4qCgINfDc7u3gINjz7D40qVLXLp0qc82hcciIiIiIiIiIiIiIiIiIl89z7A4KCiIESNGMGLECJ8Quc8+gQTHnoGx3W53PbwDZBERERERERERERERERER+Wp5B8Ymk8n18AyQ++wzWHDsHRpfvHiRE+c7qfv8GM09HZyzX/DeRUREREREREREREREREREvmJjTaNJDIlhzm2TSRgTxciRI/sNjwMKji9dusTFixe5ePEiv/v8Y147c5AFUdP527A7iRg5xnsXERERERERERERERERERH5inVdPM8fzn7Crs5DPB55L39/20RGjhzJyJEjXeGx04DBsbPa2G63Y7PZ+LjnNC/897/z7B1ziRv9Te/mIiIiIiIiIiIiIiIiIiJynWm98GfWfbqXNf/j/zAxJJrg4GBMJlOfquMR3jt585ymuu7zYyyImq7QWERERERERERERERERETkBhE3+pssiJpO3efHsNvtXLp0icuX+9YXBxQcO8Pj5p4O/jbsTu8mIiIiIiIiIiIiIiIiIiJyHfvbsDtp7ulwhcYBB8eejZ3B8Tn7Ba1pLCIiIiIiIiIiIiIiIiJyg4kYOYZz9gt9qo09M+F+g2MnZ2PvxFlERERERERERERERERERG4s/eW/gwbHeCXNIiIiIiIiIiIiIiIiIiJyY+ov+w3q7e313eqxg91up7e3lwsXLrDksx1UfPtx76YuJy90UX3mfZq/+Iy/Xrro/bR8hW4ZMZLEW28nPfJuxo+O8H5aRERERERERERERERERL7mFn/0Gltvf5TRo0czatQoTCYTQUFBBAUFBVZxHIiTF7pY/+k7vHeuVaHxdeivly7y3rlW1n/6DicvdHk/LSIiIiIiIiIiIiIiIiI3sasWHFefeZ/zl2zem+U6c/6Sjeoz73tvFhEREREREREREREREZGb2FULjpu/+Mx7k1ynNFYiIiIiIiIiIiIiIiIi4umqBceanvrGobESEREREREREREREREREU9XLTgWEREREREREREREREREZEbk4JjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbnIJjEREREREREREREREREZGbXFBvb+9l740Aly9f5vLly9jtdnp7e7lw4QJLPttBxbcf924KwOKPXvPedO2Nm8+vYscT6r3dw8k/b2Tpp95bDfPvWsaPbrvFa+tf+aC9lNWnvDZ7WDl1JXNG991m/byRf/yvQ303BtA/J7/7X0P9jeMVsTZQnFmBxQ6TsktYNy/Mu8U10ETJ9zfTCBA3n5e3pBHt3eRGYW2i/Cdl1J6C2LRnWLdsSkDnzrXSvquQZytPYB07heWFeaTEebe4QdjbqMp+jp2nIHROHq8/PcW7hQym6yi1lbvZ+x+ttJ+zAcGEjo8jJfNJfjA7imDv9teYrcPCa5VneWj1nBv3eh+i01X5/HgbZJUWkj7e+9kvyYEyFrxgIXlNBTkzvZ8cQNdRqjd8QOwLC5kOQAfVy/Op5Ev8zLaf5fibZRz6mzwy7/V+UgLmcxz9jKW9g/q1JVS+34nVDrGZP+DOqjdonL2CXXlJ3q94ZXz6c51cKyIiIiIiIiIiItexxR+9xtbbH2X06NGMGjUKk8lEUFAQQUFBN3jF8aVL+E29PYz/5kp+dZfxVbXbA5QlrfQTGgPcwndiV/LO3fOZ7/3UuPn8Ksk3NAYIvS3Zd58A+ud02R5oy+tXy87/h8UOmKaRPufLCI2/Xk5X76b2lA2w0V7TwHHvBleg+3ANlesLWZn5HNUnvZ/1p4mdW09gtTtCw993eDe4Ydj27WLnKYAo5v6DQuOhsu4v44msIsr39RCbuoDcvKXkZqeSSCvVRXksK2rC6r3TNda8rYzak73em+U6dbp2G5WHz3pv/nJ1NPBv245yyu79hAxJIMfxwG62NHUSMXsh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MUAMc5/PN/riuV7uqBBip84KvBJYRERERERERERERETE4cauOJZh628cB2fDsvZJig8SWFWnXFX9VrEKLZV55L7ZCaZp5O54JsB1t0VkuFRxLCIiIiIiIiIiInLj+fpWHMuX78y7VB80fjQvSlNoLNcH+1Hq93QCEL1ggUJjERERERERERERERGRIVJwLEMTOYd1eyrYtaeC3PsHm6Ra5EtimkL2m8Z5+XLWBO9nReQaiM4sZNceVRuLiIiIiIiIiIiIfF0oOBYRERERERERERERERERuckpOBa5gUzPM6pqVeUnIiIiIiIiIiIiIiIiV5OCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm5yCYxERERERERERERERERGRm9xVC45vGTHSe5NcpzRWIiIiIiIiIiIiIiIiIuLpqgXHibfe7r1JrlMaKxERERERERERERERERHxdNWC4/TIuxkzIth7s1xnxowIJj3ybu/NIiIiIiIiIiIiIiIiInITu2rB8fjREay+43vMGBunqZCvQ7eMGMmMsXGsvuN7jB8d4f20iIiIiIiIiIiIiIiIiNzEgnp7ey97bwS4fPkyly9fxm6309vby4ULF1jy2Q4qvv24d1MREREREREREREREREREbnOLf7oNbbe/iijR49m1KhRmEwmgoKCCAoKCqzi2NlYRERERERERERERERERERuXP1lv4MGx84d/e0sIiIiIiIiIiIiIiIiIiI3jv7y336DY8/GQUFBjBgxgrGm0XRdPO/dVERERERERERERERERERErmNdF88z1jSaESNG9MmBnT/3Gxw7ORuPGDGCxJAY/nD2E+8mIiIiIiIiIiIiIiIiIiJyHfvD2U9IDIlxBccBVxw7OUNjk8nEnNsms6vzEK0X/uzdTERERERERERERERERERErkOtF/7Mrs5DzLltMiaTqU/VsVNQb2/v5T5bvFy+fJlLly5x8eJFLl68yO8+/5jXzhxkQdR0/jbsTiJGjvHeRUREREREREREREREREREvmJdF8/zh7OfsKvzEI9H3svf3zaRkSNHMnLkSJ/wOKDg2Bke2+12Ll68yInzndR9fozmng7O2S947yIiIiIiIiIiIiIiIiIiIl+xsabRJIbEMOe2ySSMiWLkyJF9Ko6HFBzjJzx2Pi5dusSlS5dcz4uIiIiIiIiIiIiIiIiIyFfLGQqPGDHCtSyx8+EvNCbQ4BiP8NgZIHsGxgqORURERERERERERERERESuD85g2DtA9tzmLeDgGEd47PyvZ1is0FhERERERERERERERERE5PrhDIe9w2J/oTHA/wf0qsMboSHotwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here there are other subscriptions to try:\n", + "![image-2.png](attachment:image-2.png)\n", + "![image-3.png](attachment:image-3.png)\n", + "![image-4.png](attachment:image-4.png)\n", + "![image-5.png](attachment:image-5.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "
\n", + "Go now to the ETSI MEC Sandbox console to see the callbacks
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## (4) Gracefully terminating a MEC Application\n", + "\n", + "\n", + "\n", + "
\n", + "When the MEC application instance is notified to gracefully terminate, it provides to the MEC platform \n", + "that the application has completed its application level related terminate/stop action
\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZIAAAAxCAYAAABApFyMAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABlPSURBVHhe7d1/cNR1nufxZ6dDL+GHO2bwtmGImWErsjWZO4GCMQNDsTkYQU9QRglnXBbBIxaRMreKg7hWLFNZIys6EzaEMjMGuaxwCTowcYZfA5vJZWCicIA3xtrAHSeExa4Do4PBpDrp7vvj+/12f/vbP9LhN8nrUdUF+X6+/enP5/P9dKry7ne/vy6/3x9CRERERERERERERCSBNOcBERERERERERERERE7BZJFREREREREREREJClXvNIWoZBxyPpXRERERERERERERIYGl8sV9S/OQHIoFAo/gsFg+P8iIiIiIiIiIiIiMvi5XC5cLhdpaWnh/7tcrkgg2QoaBwIB+vr6CAQCBAIBgsEgVruIiIiIiIiIiIiIDD5W9nFaWhputxu32016ejputzs6kBwMBgkEAvj9fjhxguHvvkvasWPg9zv7FBEREREREREREZHByOMhOHkyPY88AnfdhcfjMYLJfr8/ZJWy8Pv9BP/1Xxnx938PS5bAzJm4MjKcXYmIiIiIiIiIiIjIIBTq7oaWFqir4+t/+AfS/uqv8Hg8kUByX18fPT09jHj1VdK+9z1c997r7ENEREREREREREREhoDQvn0EP/6Yr59/nuHDh5OGrT5yMBg0ylnMnOl8noiIiIiIiIiIiIgMFTNnknbsGMFgkFAoZASSMYPJwWAQ/H6VsxAREREREREREREZwlwZGeD3xwaSMYPJIiIiIiIiIiIiIiLYYsYuv98fCgaD9Pb2cunSJW4vKMBVX+883/DWW9DSYhRclsviysgwSoc88YSzSUREREREREREROSmEVq8mC8aGhg5cmR0RnJSb71FaN8+BZGvUKi7m9C+fUZQXkREREREREREROQWkHoguaXFeUSuhNZTREREREREREREbhEpB5KViXx1aT1FRERERERERETkVpFyIFlEREREREREREREhiYFkkVEREREREREREQkKQWSRURERERERERERCQpBZJFREREREREREREJCkFkkXk5nL+OJUvPsvcwmLyq447W2VI8NGwppj8wqeZ/2I9R792tg9ca1Ux+YWRR/kR5xnX0ZHN5Bdu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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Delete the userLocationEventSubscription\n", + "The subscription DELETE method in MEC013 is used to cancel the existing ```zoneLocationEventSubscription```.\n", + "\n", + "![image-2.png](attachment:image-2.png)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# GET subscriptions list\n", + "url = mec013_path + \"/subscriptions/zones\"\n", + "payload_dic = {}\n", + "method = \"GET\"\n", + "\n", + "response = requests.request(\n", + " method, url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + "\n", + "print(\"Status Code\", response.status_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscription_list = response.json()['notificationSubscriptionList']['subscription']\n", + "len(subscription_list)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "payload_dic = {}\n", + "method = \"DELETE\"\n", + "\n", + "for subscription in response.json()['notificationSubscriptionList']['subscription']:\n", + " url = subscription['href']\n", + " response = requests.request(\n", + " method, url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={})\n", + " print(\"Status Code\", response.status_code)\n" + ] + }, + { + "attachments": { + "image-2.png": { + "image/png": "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IiIiIiIiIiIiGi0YSCYiIiIiIiIiIiIiV1kHkm/gyhZDxrIjIiIiIiIiIiKiq1nWgeSC21LqU5Qllh0RERERERERERFdzbIOJJdMGcC9gRRn1w7CDV7g3kAKJVMG1E1EREREREREREREVw1PIpEYSKVS6O/vRzwex7Ptfrw8K6mmIyIiIiIiIiIiIqLryPJDXqyfHoPP57POSPZ4PPJ/iYiIiIiIiIiIiOg6pseMjUCyx+NBTk4Ocr3ABU5IJiIiIiIiIiIiIrpuXUgCuV4gJydHxI6hBZH1QPLkW5PoOpv10slEREREREREREREdI3pOitixZZAMiAiy2PGjMEDgThaPvHgvWgOZyYTERERERERERERXUcuJIH3ojlo+cSDBwJxjBkzRgSTE4nEAACkUikkk0kkEgn0fOnB4agPJ7/wIsFgMhEREREREREREdF1IdcLTL41iQcCceR/ewC5ubnwer1mIHlgYAADAwNIJpO4ePEikskkkskkUqkU9O1EREREREREREREdO0xbqqXkwOv1wuv14sxY8aIILLHYwaSIQWTBwYGkEqljL+JiIiIiIiIiIiI6Non309P/zstkKzTg8cMIhMRERERERERERFdX/TZyfq/APD/AQDitv4VcafzAAAAAElFTkSuQmCC" + }, + "image-3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABZcAAAAyCAYAAAAgGeVmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACCiSURBVHhe7d19VFRnnifwb1VBjfg2HcbslkQkYzdmTkhO1CNK49gui/FtGqIx4EiWNpIOORKPzCqJknHJCYcJOqLTOAgndAJx6OAIcbCxW9GWoRk6NBFWyEmTM8i0GyxDaldCXEVhC6rYP+5L3Xvr1hugonw/59QR6z5167nP89xbdX/13N812O32URARERERERERERERBcCofYKIiIiIiIiIiIiIyBeDt5nLo6PCIulfIiIiIiIiIiIiIpoaDAaD6l8t3eDy6Oio/HA6nfLfRERERERERERERPToMxgMMBgMMBqN8t/aILNbcFkKJDscDoyMjMDhcMDhcMDpdMrLiYiIiIiIiIiIiOjRIwWQjUYjTCYTTCYTgoKCYDKZ3ALMbsFlp9MJh8MBu90OXLmCaZ98AmN7O2C3K4sRERERERERERER0aPKbIZz8WIMvfQSsHAhzGYzTCYTjEbXbfxUwWUpDYbdbofz3/8d0//2b4HUVGDlShhCQuQXEREREREREREREdGja3RwEGhqAioqcPfv/g7Gv/gLmM1mOU0G9ILLIyMjGBoawvQDB2B85hkY1qxRrpOIiIiIiIiIiIiIpojRCxfg/MMfcHffPkybNg1BQUGu1Bmqgoqb+Bnb24GVK5WLiYiIiIiIiIiIiGgqWbkSxvZ2OJ1OOX4sUQWXoUiNAbudqTCIiIiIiIiIiIiIpjBDSAhgt8vBZSW34DLEADMRERERERERERERETzEjFU5l51OJ4aHh3Hnzh08lpwMw8mT6tKSDz8EmpqEpM40JoaQECHtyKuvahcRERERERERERERTRqjW7bgu6oqzJgxA8HBwTAahTnLujOXvfrwQ4xeuMDA8jiNDg5i9MIFIVBPRERERERERERE9JAJPLjc1KR9hsaD7UlEREREREREREQPoYCDy5yxPLHYnkRERERERERERPQwCji4TERERERERERERETE4DIRERERERERERERBYzBZSIiIiIiIiIiIiIKGIPLRERERERERERERBQwBpeJJr1BdNYcwytpGYhLyUVVr3Y5PfTayhGXkoG4tBzknLdplxJ55uhD0/vvIWFbBuJSytGiXU5Tgq0mF3EpGVj7+nso+WLsNwqW1qN95LVpS3piQ9XeDKTXTMHjWG8d0lMmftvd+qSoQ1tkEupAnmoMBfDdpa38Hh/LxjhGe+uQHsh2EBEREU0h9zW4bPrZr2FubHR//PofYPKnXGMjzI3/hCAAwFsIVv3fXWDr8fD49T8gyK/1PBj23x1DXEoGdp67pV10z/l/wjOClvf3IS5lF3aeC/DLfCD6O1B28D0kvO+pHgLbufewNiUDm9/vhF278EHpqcXWlAwk/LxLuwT235VhZ3Uneoa0S+4X4URMG2zw3ufjMYLu8x9jz+6CyX8SJwWF3R7eTkCl9tQpM9SHpuOHUOI+DNzcq31f2q8DPvG+l4Y6UZiRgbht7+G0ts385dfx4T4dqwLl5fjQ/clR5DRex8CwdsnE4Hi4t+PB7XNUfngJrolBzLi9ddDWyn77Oqr+vhRnb2oWBCI8EScqi9EgPXZHo/5IIAFm/7QU3avPkAfD9tklIDYaaOlw65exstXkYmt1OPIV/ZGP0knXbuq+7EBeSimuJeW4xlDBMlzM0vnMm+RsNbm6+xkRERERqd3X4LJzVPuMaOYSmBRBWo/lAACjcOj+6y6w9Xgy6ud6HoTr+EV1J2CKwpa42dqFU8/VdlR8fu+CLPfOCJqq62DDHGxa85R2IS53dAIIQXzGAZyvzEFymLbEo6YP7f/6KS7b7moXPLqWbkdD5VF8tGkegEG0f+nrVJb7fsAeyeODDe2X+wBYkJZ3FA2V2xGjKUEePLTjQZ9lUw4aKg4gNzYEcHTh8n9oS4zD0u04kWRBfTWDbJ514INqYPWLizHfWosPJigQ/9XXNiB2sWq/jnkxEZHN7Z5/fHjQem24BgtWL7e4ngtbh7RYGy5+NhlGkAXJB4tRuklRP3+ErUPplPgORkRERBS4+xpcljj/5z+i54c/FB//iKEBAIiAseItL+Wkx8sI9IJP7+s5iG/l585AOM/8CgPSc2sy5eCx9/U8AF80ouYGYPnxC1g5Tbvw3rNsEmel7I7WLtIIQszrB9BQeRRF6wP8Mn8PWNa/jfOVxTj1ehTM2oUPQn8jTl4GsCQB/y1Cu1DyDFb/5ewHVF/hRKyhshgnkoT+i5RmJO1cpC08tSzdLs7MSkc8ACBanGE2lhPQIESsWIJI7dN6HvC+f99Ni0JmcTEajr+NjQG3ayAm17EK8PP4EL4Mzy94kNfQ3GeP0HiQP0cLEoV9X541PIYfCkyzsTLmGe2zE8ISHg5Yv8FX8jPqtAeeZzXrl7PV5CK7GUCzchauflldmqtGPM2sF2bU1imuvilHi+p91DPEVTPJtTNWpRnjKRnuV560taM+fBl+FLYIq2OB+hbFzGIxzUOVYt3ytkmpFmpc26PcliefsADaQHLYOpRK48MtVYOwbcL6pfQPda7tVWyTftsoeNretnLE7S1HnngFzof/pOnLMAvmwz2QHLPTFdDVzlrXmyF8Ta+9pNfqbA889V9vHdJTypEnvi6vTZkWw0sbtZVja7UNsNZi69462Dy0tVsdxXJ5RYq6eJpp7lY34WnVNipfq+oT7T7ioT46aUCU7d9SlIH0onL11RCe+t5b3YiIiGhKeyDBZRiMmGs2i49q3C1rgxMAQueq0mOoy0mPIAQcS/G6HiMek58zwCC+5E+k54JNrkbyup777RbO/sunGMACbHl+nuJ5bZ67DCRkvIfCz/rkEsKX71xUNNcjZ/cuoVxabsBl/KH6EurpBNDRh8snj+GV18X3SclQn+RovkzHpexCQtYxVHUJYX35ZOJIq1C+uVT3i6/b5cd6X4pvdqHqYA4SUsUyaTnIqenCgPQLg3hCm3exEyX792BtSgbiUnch/XgXBuSVjMDW/DF2Zii3x8O2A+g8fRadCMHGtdEBB4+17RuXtg873+9Av7f6bstBTs1Vub4T09diqoe9tWg6J+WHzsDazGM4a1UUu9mFqoO5Yn5YbV9LYzcXJVZhnSVZUhnFyY2P8eB3XQD0f16LnCyxXcSH6mRNlc9WGg+utvNX/2cnsUd+n1yU+Lg02D6ifUbJ077vq331UxxIY0gbyLEEf4uqgzlCnbXjYVhol81i27r1kchX+wrvXY4Whw1n8/eJbVyAMrkvtccz9/fwNX79PT5o9yW9/XXgyzrXe6RkIGH3MVR9qUhLore/PcDjw8CXdW7tr3dMnBzjQezrog7A2ojsDLGN361Fp3wRw8M1HnTrsm0MedUdfWg6XuD6jNxbi25tGa2JvqQqbC4iYcW1XojHWEXag4JEXDvi3hfeylk25SA/FkBsuvgjpeey7jqQd8SKHQWutB3d1ec8z+RtvgRkCj/67QhvRXZKO1bLPwq2okzq27ZyVQqK/PBabFUGvrNqMX+39J7hKMlyHVtbWloRGbMIFo8zi1tR8nWiIs2IcttsKGmZK/yoUJAIVOfK+4VlU7pYZ3EsagPefuiuvoSFBeL2Q7lNAJprcSVJ3N7YVmTL61dv74kkqLYX1lYgSVjnqz/R9uUi7N8dje5q/eCwfzy0V1s5sq2utC354bXI8av/WnHtCWFs7V+qfB+BbhuJM/YRnogTB9dB/fOSZrzq9Gk9xPoXJCKyudRLG6jrZqvJVWxjDnZYS12B8ELFGCxIxLUjUp/4qo933c1AWmUxGg6ug8XLWPdcNyIiIprqHkxwWcWAmZ/YhODyzFDVEuOSN9xzHGtmN/tjsq1nQvT8FhVdwMy4BGxUN5ubgZvXcbqwWPMl04ayolNosolRrCEbThceRVlPoGXG6xbO5uVgzy870XPba0RNYQQDvZ0oyftntEzkCfRQJw7tLUTJ532uYPJQH5qqC7HtuDrXaX3ZMVRdHRRyNjuEHMFVUrt8eQqvFX2Kzpt+bM9QK042DAKRG5D6rHYhgJutuNgBYPp0zNIu0zN0C52NpdhXq/6yr6rvcB+aqgvw979T1m+C+tpah5wKV35o+41OHDrZKua2voWzhYUo+dw2wZeiexgPXusC9J8vwOaDdWjqFdvFzSDqC3LU+WyHhLbb/Sv/T6b6zxdgc2EjLivfx9O4DZ0FC4Cetkvo8dRGHvf9iW3fppPHUPJ5n1BnzXjorMhHTuN19HvJA+67fSVW/DL/EA59IQblhq6iovg3voNnKhM0fr2wXy7Htrxa13sAGLB1oiTvXRR+qSp6344P9p5LuGgFMD3EPfB8sxHv5NX60f7+uW/jwdqI7LyTaBFzBg901SHvzHVtKR8m03jQ1GW4D02/OIUmL22lJnxG5py/6vqM9HT8AIDQ2bAA+Kyl1fU5NtF6O3DRGo00Ka2Ap5QH/pZDgGWxCPsVV4fYrJpfDbViE8WyFswPhyLNhAULw13FWlpaEZm0Xp4xrgoSt7WjHtFYLQUml25XzC7vwMVmRRqIsEVYHd6Ki6pgogU7XhSv9Fm6HjvC1dsWnyQGL8PWIU0189l15VBDQSIirUL+dd0fxz1RbH9yUrR6JnR4In4qblPMi4mItF7Cv/W6b69lUyLiodwmRVvoka/qKUZ+LFB/RP/HIM+8tJci7YhqNnRLqyqFSMxO5dVVmjQdWt7aSE9vBy5aFevU1lFZ/zAL5ssv1KOsmw3/1mJzjQexPt2KPN5yehrVDHZf9fFBmXrF41j3XTciIiKauiZBcBmQpwsDQpDZm1GvCZD9N9nWE6DLFxo95OBchP3iF/qGymI0HD+AzGcBwIYr2i/1EfE4XHRUyNO4PARAH1oua74i+lPGi5id0qwXD1/qvzwn3LxsVhTezD+M83LdFZcEh61DqXKbyjKRHArAYcW1/62TniM23VVWkbbBrZxGf+M5nL0NIGIdSsuK0VB5FKd2r4AFQH9Ds/pEwzQHG7MOoKHyMN58FgD60HtDXDZ0V5ilOGseklMz8dEHR9FQqZ/fr7+hEU0OYOXzq6D+jUCcfZtRjvr/F4QlG1chSrVcILWv8DiKU9uE8dD9taaPZkXhzYKjQl7fF4R6NHX8QV1mnH0tCX16Mz46XoyGd1YJJyA3vkU/AMCOATGgErE0Ebnv5uJ8hbKvpbGbgx3hEE7OpJlpyjQTPsaDkue6dKGi+iqAEKx87W2cOe5anzyrqec3+OBzAI+vQH6R0Idndi7CTADdTf6eTF3H6TPi+6TliNsrbZ+OacuxZb0FZmsdXtmmH0DwvO/7at8AzfM8Hm4PCTOLQxesQma21H7KVCB+tK/MhpauP0PqO4fRcDwd8SYAN6y4NgSdMeGFh/Hrtt97OD54P1bdwsUzregHELnpbaFdjx9AftwcAIM4/dtOdfF7fnwQZvCuza5Dd/AcbPyrZZrlAIZGcBtCXeKTtqO0UHifMaeyuV/jwdqFltB4FH1QjPOZQl1tX/WIAemHdDzMegqZ+UfRUJElHqu+xTfCgci3q79BRReA6dIxXJFCQ8+CFUh9djYGPitHQqr+jOsx6f0G3QjH/DDpb8Vs2pQMZDfrfO74Wy7QspqbzOZ4jQL6y4ZrVqhm28Zl1aJbnK1ts1qB8Ll4UvsyALaaWtRrrrQpsSqCgAAgtR3gCnLLLFioSO/y5BN6Y07x2edzJqxapHJ9YXPVYyfc4pqRqwiCetveQEn7Un6sDSWF/s689tBeS7fLN5hUz4oW+k+1rQHw2kZ6lPvDhLLhilUKxouPI61iShoLkg+KM6ulZXIai4mrj+e+91Y3IiIimuomR3A5OUxIhzHwrWpCjm6O45SDihL+mWzrGbf+epQ1DALPrUWyNgen4xY6z5W6Lrnetg+FX2jKiCKXrcCS0CDANBuR8/9Uuxjws8x42K7+EQMAIv7rC9gQoTP7DuJML+UlwWmFqPL3xDwAV/54FQCwMmEDIqcBQBBCl0YJQV3tDLDlLyJzyWwAIQjVTile8tcoSopCxPB1VFUU4pWf7nK/jBwAHF2oOHUVeDweqT8cW77UgS/rkbd/n5jGYxc2a2ZYy55dgQ1hQUJe3+/rR2Umpq8t2JIWj4hgALP+VDPbeg6Sd6dj44IQfNNWi5x3crA2dRe2HqxTXPbuB7/Hg5e62K4L7/n4KrwaNw8zg5ULRTe+FU7abnyK7J3CeyUUdQSYEqMPvf0A8Aw2rLbArMr7o2cYt+96mdHqbd+fqPYVeRsPMa9kYcdzc2DvaURhvpA2JGHvx2iSAqj+tK+CZf1PkPZUCBC8CPsritFQ+QbiA8w35K2+42dF538AQBRSN84T+jF4NmIW/7mw2KmZKj4Zjg+WeBzIXIGo6TdRX12O9MxdbilxAuGtfSd2PMxB6mubETUdMC8XA797V+h/Nnjhrb7jF9h4iFy3BRsjggDTdPzZDNUi3/pvCcehRdIx3AcHMGAfww7vQ0tLqyvgFDYXkXJ+ecVD+8OFv+UCLdt2DiVWV9nSJP3PtMAIAUz5fgLyQ/iRxD3ntESczSmlEJAeBYpZwAAgpxQRXnNNNdla/eP/V3JAvQN5erN9fc6EVVMF6Hu/UV8VYrW5gr29NlwT//S8vb7ZanJ1fxj1GDT3SdNeilnRJ5IsqD9Sjhax//R/jPDNaxvpUaWJmUjCbHq38ST/SKyYyV6ZjnirmBZkAuvjue991Y2IiIimskkRXA5KWAIDAOeV30M1D1gvx3HQGKo82dYzTt2/aRRycG5YgZmaZbban2FnRQfscW/hfGUxzhdmIc3HFAx7fxfOXhK+WM+aPl27GPCzzFhYwuYCAHr+9Zc426N/uXTL+znIOX8NP9h2EA2VR3Emfzvi3abpKdz+v2O6HPjJ+UL+2qYzZ9E9BAAj6G9uRzsCSEsBAAhB1KY38FFZMc5/8DbyX3gKoUM2nC78CGfFS70BwP77izh9F4ha9zwi3YKO4glEcTrip4/g8kmdfJK9ddiddwr1w7EoKitGQ1kuSl9YoC2lNmTD2d8KM5ZnTg/RLgXuYV8DAB5fhMy8wzhfcRSn3n0ZGywjsH1ei33/rBcUv4tvdVIHBDwe9FgsQpvfaMSHDYq0F0rTpwv7V/gqHJZmfUoPt/yLngTjT0wAYEV3z4iYb/cczno6Abx5CdWNfbCHrxNmXGsCK972fcD/9u2+8kcMYAS2hlIc+ky1yI3ueJi+AMl7c3Gm4ijO5Kcj9ekQDFg/RU5xozAz3J/2VYhaqMkdPQ669VUa0/HhPyNiHgB0ouL0ddgdAIZvoanF+76kbyKOD8IM3vP56xDp7MPpU826swFDl7+MovePCseG16IR4exDU/UxfKhJ4zG5xsOf42kfh7FA6NZX6YGPBx+krxjWa0KqnOE+NJ1u9hz86mpERdcIZi7fjjMV+jPiA9ZWjuxmZeoGIe2DnKtYnEnvNkva33IIsCygCNbaUFUt5s8ep5gYde5mIWe2mNN26WJ1Wgjp5m4dQjoPtxQRYYuwWpWWQPF32zmUKFMYQJEGo7cOZc1AfIyQu3h1rE2d6xjSTGnxPbU3z2trR72iLABFigexrZQpEBQB8JZ/qUV3+DL8KMx9e1Xv6YNl+TJENmtz8Xbgg2qbnJdafaNCIUCvpsyF7WovW436xn+W8HD5R4+YGHU6C21Zr7y1kR5t/+r06dhY8KMYi2rWe0uRNENZebNGoezCcGB+uMVHfcTAu5y+ogMXm6V16PA01nu91Y2IiIimOj+mwUw8IXfxG5pnv8LQrioEmVw5MvTLDcB57K8wUiX9/0kENzaqi6AHzlU/kSeb+rce3yZqPeMy1IqKX/UBj6/DBp0cnLcHhRlL3TXvYW2Ndqlad3Uu4qoVT8yKRtKq2YonvJWxoWqvdOM1UXMp4poBIBr5ldsRgw7kpZSqTnTk9Uk3SHnueaQ+3oqKG504lL0Hh+SS0jpGcOcOAIygvmgP6osUK9OSTsK/OImE1JPC36qbBfmob+wKLDl5Epd76pCeVqcoCER6SEuhx1aTK9xhXMsUglnybMzr+EV1J2CKwpY4dZurfG8RVj8L1OudCAzdES57t9bhlW3q+rqRt1VkmofUdeq0CoH0tVxWbl9/uI8HycwZyul8QWJf3kJV3i4Iu5YFOwpykBwWwHjwKgob18/B2V/1oenn76Hp564l8bvFS/WfXoXUiEaU9DRiT6b6GBOZlCMEbdrKXTcGAwC0IjulVVHfKGyIC8HZizaUZe9CmaKkrruDuA0gMmaZMONayce+70/7Wp56CjNhw8DnHyMh5WNtMZVAxoNshhiQ96d9/eHWvsKl5yXKPhB5rq/I6/HBve20x6rV/2UBSo5fdT+26uxL3kzk8cEcsQyrw+rQrZ05Db22k8zALHF343iYhOPBWoutKbWuz6XnYrFxVitO+3OcB4C7QtqV+BXRmOn2o4Sf5DpIhBRFrjQnFiQfTMeVFEUbx6ajYZMFUIWZvJUTg4FHShFnFdrUW1mVpeuxIzxX7HsLduxOROSRS7jWC8SMJy3A0u04kZSLrSkZ4hMW7ChQpG0qSER6VgbixKXxu4vxo/+Vi5LYRJ1ApBCIK6k+h5bdABCN+V/nIi5FWBq/W2zPXqFsPGoRl1IKiGNZ2ididhbjxBPKOoljsFL6gXMRfppkwVb5MzkRO8KBK67SiIwFylIykC29VvmZHRuOK/I2RSNfsV719irbwp22L0srLchT9qW4zaXKHM7Vpa7Py6RoqH9Bj8ZqlCIuRRhPcnttykH+1xmK8SnUywK9/hO3p9f3fuOpjSzLlyGyuhZbU75BfoEwGULgPrbVfTp2FrdtdPWLdgwiNh0NS+G9PlI+7SzXsWVHkgX1X0sr0dL2vbf2V44ZIiIimsru/7RbPQNt+O6HL2NEEVj2xuEzzfGoWxYDPb7X45+JWo8/5BycSRt0c8JFbtyO5AVieglTECKeWoW0VR6+9kknnsEhiFq+GUUHtyNGeym6P2XGwzQPaflZyFw+D6G66w1CfMoWrLSIv4OI9UjWC64t+WscXuvrkmsvQlchP+9lxEvtB2Dm9+Zh485cFP3YQxv6Q+yH3EOvY6W0jV80ouYGYPnxC67nArVgA95UbO9MywIkb1oBt2wJUPSjXJe3FIECTZl71dc6zLPmIT41Ez9/STlzdQ6SMxR9rhLAePAhMiUbRamLEPU9vfeBcLL2P4SxOeYxBSAq9b8j87k58j4ZuSodh/UCJj742vf1uLXv04l4d61FCDqZghDxXCLeXKuuizni+4gKVaSo8Wc8TJuDJWu340RmtPw63+07wXyN33EeH0LX/g0+em0RomaJ22MKQuiCFfr7UiDu1fHBTRBmLliEzP1ZSJMOEhwPk288aJmewo63N2Pl4+L7TLNgY+YbSJuITBA65JzUqocyf7ZEc38HxY3Tkg8qZ0x7KqdIbyBfCeKlrIoyNUAOkpeuQ6luHbU3ddP+X1tX7fZr1qnJ979/qVjeQz2FdbkCsqsV90jQ/qCy8EXX+2pnm7v1iebKGdXyneuQfFCz/ifWu+rtdtXNYkWba4LHqu1VtIXqZobK57z0pds2K5fnIHnTdtdrxfUnK7ZL+Vr1vSbUfaRuK7GOYdrx4d7vHttIboPtiHFbj3ob5Tp6KKftc0CvrEC9jV7uQaIaex7q4/Y6sW3F12r3Effy3trf8w8ORERENLUY7Ha7HBp1Op0YHh7GnTt38FhyMgwnxdk8CqNbtmif8pvDMYz/oxeJNZjwn4JN8rmgx3IAAANmBQdjpsGJ7+wj0L/huhGPmYMQ7Nd6lM9J6xReL52H+lcf7fP+02tnXY4uFGYU4rRjBQ6//zKWjHFWkjRTSjvTS8mfMjRWt3D23X041LUAmUVZ2OgjpUNLUQaym0MQn/kO9i/3PItRlzTTz8sMY/b1ZDEC269+hq2VV937YoL2/UfRozd+Azk+iLOGe+chLe8tpEbcp8DtJPbojYcAOG7hclkB9jT0BTYjnO6ttnLEHYF4NZZGbx3Ssy5hdYF7gHH8hOPDxRj9faGlKAPZ8PzdYGrw3kZEREREpDa6ZQu+q6rCjBkzEBwcDKNRmLN8X2cum0zB7jmLzWbMVQSWvZYzmzHXLAVyjXjMbZn0EALD/q1HSVqnK7CMMa3n3rD//iJO3waiNicwuPQw6/ktKrqAmXEJPgJHgoXfXwBgEPWF+xCnd3Mfevi1lSMuZRe2Vgo3lZwfrj7J5b4/hQR0fLDgBz8IARzXUZa9y5UflqYcW00u4lL3YU9DHwALFk54oJKIiIiIiEjffQ0u0/iY//INNFQWo2h9gLNXaXKJSMSJymKcec2/vJyha15B7qqxX75ND5Fpc7AyKQt/o5lxyH1/Cgnw+LDk5Tew47k5Y8+vS48U86x52JiZcQ9mwdKY6aWRkHhIizAxdNI/KOimQ5hyvLcREREREfnnvqbFIH167UxEREREREREREQ0GUyKtBhERERERERERERE9GhgcJmIiIiIiIiIiIiIAhZwcNkQEqJ9isaB7UlEREREREREREQPo4CDy1i5UvsMjQfbk4iIiIiIiIiIiB5CgQeXX30VhjVrOON2nAwhITCsWQO8+qp2EREREREREREREdGkZ7Db7aPSf5xOJ4aHh3Hnzh08lpwMw8mT6tJERERERERERERENKWMbtmC76qqMGPGDAQHB8NoFOYs685cNhgM2qeIiIiIiIiIiIiIaIrSixm7BZcNBoMQeTabMTo4qF1MRERERERERERERFPE6OAgYDbDaDS6BZhVwWWDwSAHl52LFwNNTcrFRERERERERERERDSVNDXBuXixHFxWBpjdZi4bjUYEBQVh6KWXgIoKjF64wBnMRERERERERERERFPI6OAgRi9cACoqMPTSSwgKCpJzLUtUN/SDeFM/h8MBu90OXLmCaZ98AmN7O2C3K4sRERERERERERER0aPKbIZz8WJhEvLChTCbzTCZTKoAs1tweXR0FKOjo3A4HBgZGYHD4YDD4YDT6ZSXExEREREREREREdGjR0p7YTQaYTKZYDKZEBQUBJPJ5JYWwy24DEWAeXR0FE6nU/6biIiIiIiIiIiIiB59yvvzSX9rb+inG1yWSAFlBpaJiIiIiIiIiIiIphYpmKwNKku8BpeJiIiIiIiIiIiIiPSob+9HREREREREREREROSH/w+etyq95nyavQAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete the AppTermination subscription\n", + "\n", + "The following method in the MEC011 Application Support can be used to delete the ```AppTerminationNotificationSubscription```.\n", + "![image-3.png](attachment:image-3.png)\n", + "\n", + "\n", + "But first ywe need to retrieve the subscription ID:\n", + "![image-2.png](attachment:image-2.png)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "url = mec011_app_support_path + \"/applications/\" + appInstanceId + \"/subscriptions\"\n", + "payload_dic = {}\n", + "method = \"GET\"\n", + "\n", + "response = requests.request(\n", + " method, url , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + "print(\"Status Code\", response.status_code) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "len(response.json()['_links']['subscriptions'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "for subscription in response.json()['_links']['subscriptions']:\n", + " print(\"Subscription Type \" + subscription['subscriptionType'] + \" \" +\n", + " \"Subscription ID \" + re.findall('/([\\w\\-\\.]+)', subscription['href'])[8])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "app_subscription_list = response.json()['_links']['subscriptions']\n", + "payload_dic = {}\n", + "method = \"DELETE\"\n", + "\n", + "for subscription in app_subscription_list:\n", + " response = requests.request(\n", + " method, \n", + " subscription['href'] , \n", + " data=json.dumps(payload_dic), \n", + " headers={'accept': 'application/json'}, \n", + " params={}) \n", + "\n", + " print(\"Subscription Type \" + subscription['subscriptionType'] + \" \" +\n", + " \"Subscription ID \" + re.findall('/([\\w\\-\\.]+)', subscription['href'])[8])\n", + " print(\"Status Code\", response.status_code) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/demo6/python/notebook/MEC application.ipynb b/examples/demo6/python/notebook/MEC application.ipynb index 2d83d71b2a920e8c7c7898bc127a864f7da77e7c..c9917c576135a31d099493f4343a624398600492 100644 --- a/examples/demo6/python/notebook/MEC application.ipynb +++ b/examples/demo6/python/notebook/MEC application.ipynb @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "scrolled": true }, @@ -100,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -140,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -153,8 +153,7 @@ "LOGIN_TIMEOUT = 3 #30 # Timer to wait for user to authorize from GITHUB\n", "LISTENER_IP = '0.0.0.0' # Listener IPv4 address for notification callback calls\n", "LISTENER_PORT = 31111 # Listener IPv4 port for notification callback calls. Default: 36001\n", - "CALLBACK_URI = 'http://mec-platform2.etsi.org:31111/sandbox/v1'\n", - " #'https://yanngarcia.ddns.net:' + str(LISTENER_PORT) + '/jupyter/sandbox/demo6/v1/'" + "CALLBACK_URI = 'http://mec-platform2.etsi.org:31111/sandbox/v1'" ] }, { @@ -166,7 +165,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -206,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -257,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -303,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -338,18 +337,9 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "invalid syntax (92138252.py, line 1)", - "output_type": "error", - "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[36], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m script echo skipping\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" - ] - } - ], + "outputs": [], "source": [ "script echo skipping\n", "# Uncomment the line above to skip execution of this cell\n", @@ -412,7 +402,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -454,102 +444,19 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 7, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-06-19 09:02:23,282 - __main__ - DEBUG - Starting at 20250619-090223\n", - "2025-06-19 09:02:23,283 - __main__ - DEBUG - \t pwd= /home/jovyan/work/mecapp\n", - "2025-06-19 09:02:23,284 - __main__ - DEBUG - >>> process_login\n", - "2025-06-19 09:02:23,285 DEBUG Starting new HTTPS connection (1): mec-platform2.etsi.org:443\n", - "2025-06-19 09:02:23,443 DEBUG https://mec-platform2.etsi.org:443 \"POST /sandbox-api/v1/login?provider=Jupyter2024 HTTP/11\" 201 0\n", - "2025-06-19 09:02:23,443 DEBUG response body: b'{\"user_code\":\"sbx19g3u4u\",\"verification_uri\":\"\"}'\n", - "2025-06-19 09:02:23,444 - __main__ - DEBUG - process_login (step1): oauth: {'user_code': 'sbx19g3u4u', 'verification_uri': ''}\n", - "2025-06-19 09:02:23,444 - __main__ - DEBUG - =======================> DO AUTHORIZATION WITH CODE : sbx19g3u4u\n", - "2025-06-19 09:02:23,445 - __main__ - DEBUG - =======================> DO AUTHORIZATION HERE : \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "send: b'POST /sandbox-api/v1/login?provider=Jupyter2024 HTTP/1.1\\r\\nHost: mec-platform2.etsi.org\\r\\nAccept-Encoding: identity\\r\\nContent-Length: 2\\r\\nAccept: application/json\\r\\nContent-Type: application/json\\r\\nUser-Agent: Swagger-Codegen/1.0.0/python\\r\\n\\r\\n'\n", - "send: b'{}'\n", - "reply: 'HTTP/1.1 201 Created\\r\\n'\n", - "header: Date: Thu, 19 Jun 2025 09:02:23 GMT\n", - "header: Content-Type: application/json; charset=UTF-8\n", - "header: Content-Length: 48\n", - "header: Connection: keep-alive\n", - "header: Strict-Transport-Security: max-age=15724800; includeSubDomains\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-06-19 09:02:26,461 DEBUG https://mec-platform2.etsi.org:443 \"GET /sandbox-api/v1/namespace?user_code=sbx19g3u4u HTTP/11\" 200 0\n", - "2025-06-19 09:02:26,462 DEBUG response body: b'{\"sandbox_name\":\"sbx19g3u4u\"}'\n", - "2025-06-19 09:02:26,462 - __main__ - DEBUG - process_login (step2): result: {'sandbox_name': 'sbx19g3u4u'}\n", - "2025-06-19 09:02:26,463 - __main__ - INFO - Sandbox created: sbx19g3u4u\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "send: b'GET /sandbox-api/v1/namespace?user_code=sbx19g3u4u HTTP/1.1\\r\\nHost: mec-platform2.etsi.org\\r\\nAccept-Encoding: identity\\r\\nAccept: application/json\\r\\nContent-Type: application/json\\r\\nUser-Agent: Swagger-Codegen/1.0.0/python\\r\\n\\r\\n'\n", - "reply: 'HTTP/1.1 200 OK\\r\\n'\n", - "header: Date: Thu, 19 Jun 2025 09:02:26 GMT\n", - "header: Content-Type: application/json; charset=UTF-8\n", - "header: Content-Length: 29\n", - "header: Connection: keep-alive\n", - "header: Strict-Transport-Security: max-age=15724800; includeSubDomains\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-06-19 09:02:36,463 - __main__ - DEBUG - >>> get_network_scenarios: sandbox=sbx19g3u4u\n", - "2025-06-19 09:02:36,464 DEBUG Resetting dropped connection: mec-platform2.etsi.org\n", - "2025-06-19 09:02:36,631 DEBUG https://mec-platform2.etsi.org:443 \"GET /sandbox-api/v1/sandboxNetworkScenarios?sandbox_name=sbx19g3u4u HTTP/11\" 200 0\n", - "2025-06-19 09:02:36,632 DEBUG response body: b'[{\"id\":\"4g-5g-macro-v2x\"},{\"id\":\"4g-5g-macro-v2x-fed\"},{\"id\":\"4g-5g-wifi-macro\"},{\"id\":\"4g-macro\"},{\"id\":\"4g-wifi-macro\"},{\"id\":\"dual-mep-4g-5g-wifi-macro\"},{\"id\":\"dual-mep-short-path\"}]'\n", - "2025-06-19 09:02:36,632 - __main__ - DEBUG - get_network_scenarios: result: [{'id': '4g-5g-macro-v2x'}, {'id': '4g-5g-macro-v2x-fed'}, {'id': '4g-5g-wifi-macro'}, {'id': '4g-macro'}, {'id': '4g-wifi-macro'}, {'id': 'dual-mep-4g-5g-wifi-macro'}, {'id': 'dual-mep-short-path'}]\n", - "2025-06-19 09:02:36,633 - __main__ - INFO - nw_scenarios: \n", - "2025-06-19 09:02:36,633 - __main__ - INFO - nw_scenarios: [{'id': '4g-5g-macro-v2x'}, {'id': '4g-5g-macro-v2x-fed'}, {'id': '4g-5g-wifi-macro'}, {'id': '4g-macro'}, {'id': '4g-wifi-macro'}, {'id': 'dual-mep-4g-5g-wifi-macro'}, {'id': 'dual-mep-short-path'}]\n", - "2025-06-19 09:02:36,633 - __main__ - DEBUG - >>> process_logout: sandbox=sbx19g3u4u\n", - "2025-06-19 09:02:36,648 DEBUG https://mec-platform2.etsi.org:443 \"POST /sandbox-api/v1/logout?sandbox_name=sbx19g3u4u HTTP/11\" 204 0\n", - "2025-06-19 09:02:36,649 DEBUG response body: b''\n", - "2025-06-19 09:02:36,650 - __main__ - DEBUG - To check that logout is effective, verify on the MEC Sandbox server that the MEC Sandbox is removed (kubectl get pods -A)\n", - "2025-06-19 09:02:36,651 - __main__ - DEBUG - Stopped at 20250619-090236\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "send: b'GET /sandbox-api/v1/sandboxNetworkScenarios?sandbox_name=sbx19g3u4u HTTP/1.1\\r\\nHost: mec-platform2.etsi.org\\r\\nAccept-Encoding: identity\\r\\nAccept: application/json\\r\\nContent-Type: application/json\\r\\nUser-Agent: Swagger-Codegen/1.0.0/python\\r\\n\\r\\n'\n", - "reply: 'HTTP/1.1 200 OK\\r\\n'\n", - "header: Date: Thu, 19 Jun 2025 09:02:36 GMT\n", - "header: Content-Type: application/json; charset=UTF-8\n", - "header: Content-Length: 186\n", - "header: Connection: keep-alive\n", - "header: Strict-Transport-Security: max-age=15724800; includeSubDomains\n", - "send: b'POST /sandbox-api/v1/logout?sandbox_name=sbx19g3u4u HTTP/1.1\\r\\nHost: mec-platform2.etsi.org\\r\\nAccept-Encoding: identity\\r\\nContent-Length: 2\\r\\nContent-Type: application/json\\r\\nUser-Agent: Swagger-Codegen/1.0.0/python\\r\\n\\r\\n'\n", - "send: b'{}'\n", - "reply: 'HTTP/1.1 204 No Content\\r\\n'\n", - "header: Date: Thu, 19 Jun 2025 09:02:36 GMT\n", - "header: Content-Type: application/json; charset=UTF-8\n", - "header: Connection: keep-alive\n", - "header: Strict-Transport-Security: max-age=15724800; includeSubDomains\n" + "skipping\n" ] } ], "source": [ - "\n", + "%%script echo skipping\n", "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -612,7 +519,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -637,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -678,7 +585,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -725,20 +632,12 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -829,7 +728,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -868,7 +767,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -904,7 +803,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -953,20 +852,12 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -1094,7 +985,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1159,7 +1050,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1210,7 +1101,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1242,7 +1133,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1290,7 +1181,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1353,20 +1244,12 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -1499,7 +1382,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1577,7 +1460,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1630,20 +1513,12 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -1696,7 +1571,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1799,20 +1674,12 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -1881,7 +1748,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2009,7 +1876,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2071,22 +1938,14 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -2234,7 +2093,7 @@ "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -2702,7 +2561,7 @@ "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the second sprint of our skeleton of our MEC application:\n", @@ -2771,7 +2630,7 @@ "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the third sprint of our skeleton of our MEC application:\n", @@ -3642,7 +3501,7 @@ "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the fourth sprint of our skeleton of our MEC application:\n", @@ -4633,8 +4492,8 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the fith sprint of our skeleton of our MEC application:\n", @@ -4819,25 +4678,12 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'logger' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[1], line 68\u001b[0m\n\u001b[1;32m 65\u001b[0m \u001b[38;5;66;03m# End of function process_main\u001b[39;00m\n\u001b[1;32m 67\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__main__\u001b[39m\u001b[38;5;124m'\u001b[39m:\n\u001b[0;32m---> 68\u001b[0m \u001b[43mprocess_main\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "Cell \u001b[0;32mIn[1], line 17\u001b[0m, in \u001b[0;36mprocess_main\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;124;03mThis code illustrates the usage of MEC-CAPI endpoints:\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;124;03m - Login\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[38;5;124;03m - Logout\u001b[39;00m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m \n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01mglobal\u001b[39;00m LISTENER_IP, LISTENER_PORT, CALLBACK_URI, logger\n\u001b[0;32m---> 17\u001b[0m \u001b[43mlogger\u001b[49m\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mStarting at \u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;241m+\u001b[39m time\u001b[38;5;241m.\u001b[39mstrftime(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mY\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mm\u001b[39m\u001b[38;5;132;01m%d\u001b[39;00m\u001b[38;5;124m-\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mH\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mM\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mS\u001b[39m\u001b[38;5;124m'\u001b[39m))\n\u001b[1;32m 18\u001b[0m logger\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m'\u001b[39m\u001b[38;5;130;01m\\t\u001b[39;00m\u001b[38;5;124m pwd= \u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;241m+\u001b[39m os\u001b[38;5;241m.\u001b[39mgetcwd())\n\u001b[1;32m 20\u001b[0m \u001b[38;5;66;03m# Setup the MEC application\u001b[39;00m\n", - "\u001b[0;31mNameError\u001b[0m: name 'logger' is not defined" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This code illustrates the usage of MEC-CAPI endpoints:\n", diff --git a/examples/demo6/python/notebook/ca_root.pem b/examples/demo6/python/notebook/ca_root.pem new file mode 100644 index 0000000000000000000000000000000000000000..af70a7030b30313765cc19cda76a9374b2dfa8ec --- /dev/null +++ b/examples/demo6/python/notebook/ca_root.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIUd1j1K+LsVoKjX0KL8E37zVr6BeQwDQYJKoZIhvcNAQEL +BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjYwMjEzMDgzNDA4WhcNMzYwMjExMDgz +NDM3WjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALUPXiUf5uLjCMrRQMhGPCTuymavlO7cmlgupx3hxnT81Md3CRo1TtxW +oQeAU4oKMljITTtnC2ebBColp2+FI1r2xf2meBRDmzvS8N1lPJW2aIZY+S+Vk4/R +yvwn2Jn1mnBjihmVEX5fWHBDNtzg3P8PasBYWhOfAE5Vew84TBAGM2YcMMI6qNI3 +DFZ6WW0IIiLvIG+2DTAsM2eCACPx6miKkh+vyKbblhsWmjfxzeIfdx7JhyqkU5ky +1Oj7ASKeZwz4y38OQnMI8aELKVeMEdBdgpAAbLjjvvO2rHizjASDONIKRHzYHOhj +9FevlTKJPAqBLYv7ZqUL6guRkdiGO8ECAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLTfEM6gl9hHUbwvu3a1POAig8pk +MB8GA1UdIwQYMBaAFLTfEM6gl9hHUbwvu3a1POAig8pkMBAGA1UdEQQJMAeCBWNh +cGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCEEodEJcXT0+m4ofihvo6+7WSBAcWffBej +DsI03AgBMA9sewUA6Zrx3CGtt1zw49UFUBB23XB4uxPx56/Xl1WbTtxJdOgQWwe8 +Tl1i1mFXNGToCUxVVU6BICMFa3UcICW5WF5MOBKOwhd0H7FJ0KIU83PgFYQ4o+UW +t0rlzWgIq2WUpt7kj9P3rI+ohTImScF0Tvkn5faJ7RqE0bPA4p4zYJoZAnedIZOL +pmmWRvdBsteLXKaMAKogN1kkpUI/zdERNsxkLeTQ8gAu2sO9hYxMX51ekXvQ93R1 +rOt7Cu4L7jBP0RQQmC8A1JpLaQYkdU8V6YZJBJzcw7d9A5+qHL3w +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDKDCCAhCgAwIBAgIUFNem+9uugPUNGgcuO7ItgQp6pxcwDQYJKoZIhvcNAQEL +BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjYwMjEzMDgzNDA4WhcNMzEwMjEyMDgz +NDM4WjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtSkS4kDrUUukTQdFQQXeqVR +YBceyEn/RBbsp3HOHJkeBGcWRI9+pJfLHxA/NBdvHEtipG5JznhegoMhPD0Z88B/ +os1KOYgNVkaJ8qFMFoxMQaMP3IhMjQG8y7ONbwv+t/YPyucz9yPWwC2FTz4NQ81W +TJLB3MXu/ZEH+759WK0ZIuIhpafCucgLDyzVLnPHBH2U+/TfMMdlna3yskZLIfoi +fAAnrVGc2TTbAey5MZz/vDuv2O3hY+WhJ/PI++clvUm8pJbEWfJV7JXDmr3111Uz +T/mJvSbtjYSJln+hOySDl2Vd4lR3sBmBp5x2RzudAo9W0uBRbRSqMW8uYWdHUQID +AQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUA3CfMHqJdgZdKd9nrHU9yGmomeUwHwYDVR0jBBgwFoAUtN8QzqCX2EdRvC+7 +drU84CKDymQwDQYJKoZIhvcNAQELBQADggEBAA/mzXrLKShuW+1mHJw5kT5N9kLn +jLRWSFyX3loWWvgakduoRq6Gogv9IpFR5T6/UB//HUsLYm/Ud1hmkwjCyieuu/9F +J2hPvT+oevgmRClAAksxctuUnfA11bHGk27SA+vRNBnU2Bbkg/XkNXhYM/x4NXH+ +p623SFY1cKcg/uVZfIiZOF9KD7zcF0WKhr0zqNGJYQFfJCg0u0Y+alTF8aZj6H20 +MVD4W7QZzaFB+tDrvcV6vqpsHz0IvkdpbcCFMOnaJsoTfoKaEaOuM1yQbr+ZuGjN +OIc3t7JpZCq3eKtOJ983f/wKXzaOex32W+wgsdjJvfKWV8Dsc3352200glk= +-----END CERTIFICATE----- diff --git a/examples/demo6/python/notebook/images/.ipynb_checkpoints/project_arch-checkpoint.jpg b/examples/demo6/python/notebook/images/.ipynb_checkpoints/project_arch-checkpoint.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c1f7ac205ce79bd93df8486561143d506b1e60d Binary files /dev/null and b/examples/demo6/python/notebook/images/.ipynb_checkpoints/project_arch-checkpoint.jpg differ diff --git a/examples/demo6/python/notebook/p_crt.crt b/examples/demo6/python/notebook/p_crt.crt new file mode 100644 index 0000000000000000000000000000000000000000..cb9b907923a80c7ee101efe90ddef5269cb7520c --- /dev/null +++ b/examples/demo6/python/notebook/p_crt.crt @@ -0,0 +1,59 @@ +-----BEGIN CERTIFICATE----- +MIIDgjCCAmqgAwIBAgIUVF+ZQrL/ncigQXmBt/NVK8TVeZwwDQYJKoZIhvcNAQEL +BQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y +NjAyMTMxMDA5MzdaFw0yNjA4MTExNDEwMDdaMCwxKjAoBgNVBAMTIUFNRjRkYmU4 +NWExYTNjZGZjMWMyYWRlODgzZDQ3MWUxYTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKN+nGx92n2z6Z3E2KyH4YCqejLK7Q+BI+4/a5Dn+GOUApCd3GMD +Vzry/xuSEMwz3lL/EOYfmMzRzR7d8a4cX+/vM54+1xnZlojQ/cT6E5Y3hlci0EdC +wzNShQ+spyGZ0u1IAPMvNCb+caCJwtnLjzChvBQVm3W3ETitLq17f/gihMUAoM2X +wGqknj90fUvwhUlujo0OKJkahjbMS6GawaSekSt4IRNAMKQuqgblQrhu8CkOJIMj +hvhd3XYflURhBuoOv+nyKasRO41BrLePsUNKXJu+sEkGsAo8hu8lWRDNHR8BHnky +4bFhht5ndxQNQ2G0TMpwCgTTwB1KKYFoq9UCAwEAAaOBoDCBnTAOBgNVHQ8BAf8E +BAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBS2 +4LRf1008QwxzAh3cBTHO7wcIMjAfBgNVHSMEGDAWgBQDcJ8weol2Bl0p32esdT3I +aaiZ5TAsBgNVHREEJTAjgiFBTUY0ZGJlODVhMWEzY2RmYzFjMmFkZTg4M2Q0NzFl +MWEwDQYJKoZIhvcNAQELBQADggEBAEn9mdOMF7eSL2VJP8rXMp6aqHXIs2iMiM8e +QlDqRw8La1Ow/5spLiGdpPlvHMZQ1NYhtCYFa8NPSEtwBdlqNtiXNmeByPGbPxzw +fiMeyKZfjQz01z2mLYYCWpwhAs0wvpzpMYlpntq6IP20xn2dw6NuKUVE0ZRYee5L +KdyCdZ3HuIMtVEb7mNEi+GJAJLPftyGu58HIhSk0zQCKuShx53aQtAZGAnfsYVpC +wGjT48I4jxvL9VzPVqyu7svv4may7qgjG3AlUylf29qnJf0fG2JjiXtAxfWExMW8 +Er8Tsz+4dJbsX7fecRoBjRUTsjTZZ0XBw/XpTh/JQ4TfQrS4YJ0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDKDCCAhCgAwIBAgIUFNem+9uugPUNGgcuO7ItgQp6pxcwDQYJKoZIhvcNAQEL +BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjYwMjEzMDgzNDA4WhcNMzEwMjEyMDgz +NDM4WjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtSkS4kDrUUukTQdFQQXeqVR +YBceyEn/RBbsp3HOHJkeBGcWRI9+pJfLHxA/NBdvHEtipG5JznhegoMhPD0Z88B/ +os1KOYgNVkaJ8qFMFoxMQaMP3IhMjQG8y7ONbwv+t/YPyucz9yPWwC2FTz4NQ81W +TJLB3MXu/ZEH+759WK0ZIuIhpafCucgLDyzVLnPHBH2U+/TfMMdlna3yskZLIfoi +fAAnrVGc2TTbAey5MZz/vDuv2O3hY+WhJ/PI++clvUm8pJbEWfJV7JXDmr3111Uz +T/mJvSbtjYSJln+hOySDl2Vd4lR3sBmBp5x2RzudAo9W0uBRbRSqMW8uYWdHUQID +AQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUA3CfMHqJdgZdKd9nrHU9yGmomeUwHwYDVR0jBBgwFoAUtN8QzqCX2EdRvC+7 +drU84CKDymQwDQYJKoZIhvcNAQELBQADggEBAA/mzXrLKShuW+1mHJw5kT5N9kLn +jLRWSFyX3loWWvgakduoRq6Gogv9IpFR5T6/UB//HUsLYm/Ud1hmkwjCyieuu/9F +J2hPvT+oevgmRClAAksxctuUnfA11bHGk27SA+vRNBnU2Bbkg/XkNXhYM/x4NXH+ +p623SFY1cKcg/uVZfIiZOF9KD7zcF0WKhr0zqNGJYQFfJCg0u0Y+alTF8aZj6H20 +MVD4W7QZzaFB+tDrvcV6vqpsHz0IvkdpbcCFMOnaJsoTfoKaEaOuM1yQbr+ZuGjN +OIc3t7JpZCq3eKtOJ983f/wKXzaOex32W+wgsdjJvfKWV8Dsc3352200glk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIUd1j1K+LsVoKjX0KL8E37zVr6BeQwDQYJKoZIhvcNAQEL +BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjYwMjEzMDgzNDA4WhcNMzYwMjExMDgz +NDM3WjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALUPXiUf5uLjCMrRQMhGPCTuymavlO7cmlgupx3hxnT81Md3CRo1TtxW +oQeAU4oKMljITTtnC2ebBColp2+FI1r2xf2meBRDmzvS8N1lPJW2aIZY+S+Vk4/R +yvwn2Jn1mnBjihmVEX5fWHBDNtzg3P8PasBYWhOfAE5Vew84TBAGM2YcMMI6qNI3 +DFZ6WW0IIiLvIG+2DTAsM2eCACPx6miKkh+vyKbblhsWmjfxzeIfdx7JhyqkU5ky +1Oj7ASKeZwz4y38OQnMI8aELKVeMEdBdgpAAbLjjvvO2rHizjASDONIKRHzYHOhj +9FevlTKJPAqBLYv7ZqUL6guRkdiGO8ECAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLTfEM6gl9hHUbwvu3a1POAig8pk +MB8GA1UdIwQYMBaAFLTfEM6gl9hHUbwvu3a1POAig8pkMBAGA1UdEQQJMAeCBWNh +cGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCEEodEJcXT0+m4ofihvo6+7WSBAcWffBej +DsI03AgBMA9sewUA6Zrx3CGtt1zw49UFUBB23XB4uxPx56/Xl1WbTtxJdOgQWwe8 +Tl1i1mFXNGToCUxVVU6BICMFa3UcICW5WF5MOBKOwhd0H7FJ0KIU83PgFYQ4o+UW +t0rlzWgIq2WUpt7kj9P3rI+ohTImScF0Tvkn5faJ7RqE0bPA4p4zYJoZAnedIZOL +pmmWRvdBsteLXKaMAKogN1kkpUI/zdERNsxkLeTQ8gAu2sO9hYxMX51ekXvQ93R1 +rOt7Cu4L7jBP0RQQmC8A1JpLaQYkdU8V6YZJBJzcw7d9A5+qHL3w +-----END CERTIFICATE----- \ No newline at end of file diff --git a/examples/demo6/python/notebook/p_key.key b/examples/demo6/python/notebook/p_key.key new file mode 100644 index 0000000000000000000000000000000000000000..7a74fde395d125f2145d6b59ccd5e88014681a2e --- /dev/null +++ b/examples/demo6/python/notebook/p_key.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjfpxsfdp9s+md +xNish+GAqnoyyu0PgSPuP2uQ5/hjlAKQndxjA1c68v8bkhDMM95S/xDmH5jM0c0e +3fGuHF/v7zOePtcZ2ZaI0P3E+hOWN4ZXItBHQsMzUoUPrKchmdLtSADzLzQm/nGg +icLZy48wobwUFZt1txE4rS6te3/4IoTFAKDNl8BqpJ4/dH1L8IVJbo6NDiiZGoY2 +zEuhmsGknpEreCETQDCkLqoG5UK4bvApDiSDI4b4Xd12H5VEYQbqDr/p8imrETuN +Qay3j7FDSlybvrBJBrAKPIbvJVkQzR0fAR55MuGxYYbeZ3cUDUNhtEzKcAoE08Ad +SimBaKvVAgMBAAECggEAOtt0opX4FzKykmpv+kR8iN5WWXy2NNOvxLAuIB9ySx7Y +gYli8n4cZAtgjWNbCrhNfCF4yu9rCakpp8gMnjp2yyCYu4ox04uCZsSd5tCERpq8 +kRfmVRESxqIZZtgn3q/KwEMgnGtM4hDNhRay/8cJSF+hHrXcnlcGKxolaKzCF+h5 +/KWFMofeHzTIsZ3veoJEeyCaGyAKgA46ByFC+FrXdTzDIFYye2HfI2jIryZug5Y8 +BL2rRdOeCQMiRWG+BEFUodq39jpF5D73UG4HsySvOZhflpGSjjuI45UJaf17TTGI +KaCeFbVXrYBaTcPGhGDvRZpA/MCLYGdfRITqfrmuDQKBgQDRzOD5RaNKZsNffmq0 +wl4gTWp8CTujNQSq0RWD667AHZnOi6Ul/gKXhpbahUB8DNGXR0f5SggKOdBPnAGc +wK31z3hmyg5MFMzm/zbQOwVM/rVZjTrz+diUToYSqSrwW1PoimQuRWRetBxJJLlc +ShLmY/KxEMYvQYOGEbRKlRfdXwKBgQDHf1QTYuChNf1DPew0EBGY2KyikkupfXfT +rboOUfTn0kE6JLg/J2ENboo2snLgn/8NC/yS8HaaFq4oRfNxUKd1MweyQKrdnSic +kQXK6BEOL7s/a21cMjU0Dye8PBglxks3Y50gOVM2FNlB3naJB7lqNl6RWhiHsNOZ +dNiJhXXPSwKBgQDKXSJLjl4Y79JXk+p8/FYqNkIV7hn2LLeZxB9KpdRjPqoSziO3 +57C8U3fCw3EVto+bqYfE7yGeK3HAuCvd9QRW642pKBBuVknmLhnC9IsCX1SxUkag ++kdrlepAqaffO1hOHt7OuuQ7bOMzshaQ7GoajCGVpSZkgn7InN8YaYMhZQKBgF/f +QgFbCEaREpgo/wV6H2rL3hL2qWPXhyl0GLDfFAoAUhZmJSE8hHiXAdcNZQWr/j8/ +3m6jHuwoMCPskfSH5SH+lxlD2JlN+IFddGIxqlid+aedsd2Xx1ewXNEqNBSbOfRx +a8ZbpjCcTlhIV8C8tI9ld4b0vEshYF8j9xgHyjkHAoGAONZUQTDnbhcErGV0wXBV +5Uxv3xcVkiUF1SAX+Mi2aHGYpL4DjRnHnDRe1tsx7X/uY4V4amjyFdxA7HeyNiwW +5ZC5eydzS2O3rTMuGn4pbQ8EcHfTLUiIWMYKJwfPmWKnA4GWLnHHoN6aw8paoDiG +Fhjj/OuTBoeLuA/nvKUPzQg= +-----END PRIVATE KEY----- diff --git a/examples/demo7/entrypoint.sh b/examples/demo7/entrypoint.sh index da149c796afb4457c37cd3a6bc1a6a1a4f84f978..b8de3fddc030214431b9ea0f9017cc19121dd32a 100755 --- a/examples/demo7/entrypoint.sh +++ b/examples/demo7/entrypoint.sh @@ -46,5 +46,7 @@ echo "CAM: ${CAM}" echo "DENM: ${DENM}" echo "VERBOSE: ${VERBOSE}" +sleep 10 # Wait for ETSI MEC Platform up and stable + echo "Starting '/fsmsggen ${IFACE} ${MODE} ${UU_IFACE} ${GLOBAL} ${SEC_MODE} ${BEACON} ${CAM} ${DENM} ${VERBOSE}'" /fsmsggen ${IFACE} ${MODE} ${UU_IFACE} ${GLOBAL} ${SEC_MODE} ${BEACON} ${CAM} ${DENM} ${VERBOSE} \ No newline at end of file diff --git a/examples/demo7/fsmsggen b/examples/demo7/fsmsggen old mode 100644 new mode 100755 diff --git a/examples/demo9/README.md b/examples/demo9/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6ed6929f23df328ff1885a67065473076440fe98 --- /dev/null +++ b/examples/demo9/README.md @@ -0,0 +1 @@ +The main objective of this tutorial is to demonstrate how to use the MEC and oneM2M platforms jointly. diff --git a/examples/demo9/golang/Dockerfile b/examples/demo9/golang/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..decea7cee77bd82ff87840dfe006fe67167efd70 --- /dev/null +++ b/examples/demo9/golang/Dockerfile @@ -0,0 +1,26 @@ +# Copyright (c) 2022 The AdvantEDGE Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang + + +# Some ENV variables +ENV SERVICE_NAME = "demo9" + +COPY ./demo9 /demo9 +COPY entrypoint.sh / + +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/examples/demo9/golang/README.md b/examples/demo9/golang/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0f26231cb5ea2dd40213d7630c6a629d400d12d3 --- /dev/null +++ b/examples/demo9/golang/README.md @@ -0,0 +1,140 @@ +Demo9 is a MEC application to illustrate the joint usage of the ETSI MEC platform and oneM2M platform. + +# How to use it + +## Building the demo9 application + +go version 1.18+ is required to build demo 9 + +There is two ways to build demo9 application: +- Using the docker_build.sh script + +```sh +~$ docker pull golang +~$ cd ~/etsi-mec-sandbox/examples/demo9 +~/etsi-mec-sandbox/examples/demo9$ docker_build.sh +``` + +- Manually: + +```sh +~$ docker pull golang +~$ cd ~/etsi-mec-sandbox/examples/demo9 +~/etsi-mec-sandbox/examples/demo9$ docker run --rm -it -v$PWD:/opt/local/etsi/demo9 golang +root@56c7b1ce74ca:/go# cd /opt/local/etsi/demo9 +root@56c7b1ce74ca:/opt/local/etsi/demo9# go run ./main.go +``` + +## Executing the demo9 application + +The demo9 application can be executed using the script run.sh: + +```sh +~$ cd ~/etsi-mec-sandbox/examples/demo9 +~/etsi-mec-sandbox/examples/demo9$ docker_run.sh +``` + +# Menu description + +Below is the menu proposed by the demo9 application: + +```sh +Mandatory commands: + l: Login, L: Logout +Optional commands: + T: Current status: +MEC 011 App Support: + y: Send ConfirmReady, r: Send Registration, R: Send Deregistration +MEC 011 Service Management: + v: Create new service, V: Delete service, g: Get list of MEC services +MEC 030: + Y: Get V2X UU unicast setting', z: V2X Msg subscription, Z : Delete V2X subscription, Q <[latutudes] [longitudes] [timestamps]: Provide PredictedQoS + [latitudes] is a set of latitudes separated by comma, [longitudes] is a set of longitudes separated by comma, [timestamps] + E.g. 43.729416,43.732456 7.414853,7.418417 1653295620,1653299220 +MEC 040: + 0: Get Federation Systems list', 1 : Get Federation Services list, 2 : Get Federation Service, 3: Subscribe, 4 []: Get subscription, 5 : Delete subscription +q: Quit +Enter your choice: +``` + +## Mandatories commands + +Mandatories commands are the sequence of command to execute before to execute with the optionals commands. + +### Login command + +The Login command (l) authenticates and authorizes the MEC application demo9 and creates a new MEC sandbox instance. + +### Logout command + +The Logout command (L) terminates an existing MEC sandbox instance. + +### Get scenarios list + +After the login step, it is mandatory to request the list of available network scenarios in order to execute with the optional commands. + +## Optional commands + +### Get scenario description + +This option (S) uses the index of the network scenario (starting from 0 i.e. S 0) to retrieve the description of the selected network scenario. + +### Activate a network scenario + +This option (a) uses the index of the network scenario (starting from 0 i.e. a 0) to activate the selected network scenario. This command requests the MEC Sandbox instance to start all the MEC services attached to the activated network scenario. + +### Deactivate a network scenario + +This option (d) uses the index of the network scenario (starting from 0 i.e. d 0) to deactivate the selected network scenario. This command requests the MEC Sandbox instance to terminate all the MEC services attached to the activated network scenario. + +### Get MEC services list + +This option (m) retrieves the list of the MEC services available with the activated network scenario. + +### Get application instances list + +This option (i) retrieves the list of the MEC applications available with the activated network scenario. + +### Create a new application instance + +This option (c) creates the a new USER MEC application. The application instance id is generated automatically and its name is "demo9". +This new application is attached to "mep1". + +Note: Additional new application instances can be created to simulate several MEC application. This demo application is limited to one new USER MEC application. + +### Delete a new application instance + +This option (C) terminates an existing MEC application. + +## MEC Services commands + +Theses commands provides some example of access to the MEC services available with the activated network scenario. + +### MEC Application Support (MEC 011 service) + +- The option (y) sends a confirm-ready to the MEC platform to indicate a READY state. +- The option (r) registers the MEC application to the MEC platform (optional). +- The option (R) de-registers the MEC application from the MEC platform (optional). + +### MEC Service Managenment (MEC 011 service) + + +### Get V2X UU unicast setting (MEC 030 service) + +- The option (Y) retrieves the UU Unicast settings from the MEC 030 service. To use it, the V2X network scenario shall be activated (see [Activate a network scenario](#activate_a_network_scenario)). +- The option (z) creates a subscription on V2X messages. +- The option (Z) deletes a subscription. + +## Application status + +The option (Q) provides the demo9 application status. If a Login commands was executed, the termination process does the logout. + +``` +Current status: Sandbox: sbxqvgq4q4, appsInfo.Id: d75d8536-cf94-45a6-91a3-fc5b033f9630, Subscription: sub-wwJ_uzHOwqPntaia, Demo9 app not registered +``` + +In the example above, the MEC Platform identifier is sbxqvgq4q4, the MEC application identifier is d75d8536-cf94-45a6-91a3-fc5b033f9630 and a subscription to the applicationTerminationNotifcation was created: sub-wwJ_uzHOwqPntaia. Note that the MEC application is not registered to the MEC Platform. + +## Terminate the demo9 application + +The command Quit (q) terminates the demo9 application. If a Login commands was executed, the termination process does the logout, deletes all subscriptions, de-registers the MEC appliction if a register was done and terminates an existing MEC sandbox instance. diff --git a/examples/demo9/golang/app_instance.yaml b/examples/demo9/golang/app_instance.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1092073042310d3aabf85a5c1bc376b489bbe31c --- /dev/null +++ b/examples/demo9/golang/app_instance.yaml @@ -0,0 +1,18 @@ +# This file defines the configuration of demo9 edge application. All fields are required to run demo-6 on MEC Sandbox + +# Set where mec application is running either on MEC Sandbox or AdvantEDGE. Expected fields: sandbox | advantedge +mode: 'sandbox' +# Set MEC plateform address +sandbox: 'mec-platform.etsi.org' +# Set if sandbox url uses https. Expected fields: true | false +https: true +# Set the mec platform name demo-6 will run on. Example field: mep1 +mecplatform: 'mep1' +# Set host address of demo-6. +localurl: 'http://' +# Set host port number of demo-6. Example field: '8093' +port: '80' +# Callback base URL +callbackUrl: 'http://mec-platform.etsi.org' +# Callback port for listener +callbackPort: '31121' diff --git a/examples/demo9/golang/build-demo9.sh b/examples/demo9/golang/build-demo9.sh new file mode 100755 index 0000000000000000000000000000000000000000..a4f55983174cd371ae86f08b7d5917349bfd3828 --- /dev/null +++ b/examples/demo9/golang/build-demo9.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e +set -x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin/demo9 + +echo "" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo ">>> Building Demo 9 Go MEC APP" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo "" + +go mod tidy +go build -o $DEMOBIN . + +echo "" +echo ">>> Demo Service build completed" diff --git a/examples/demo9/golang/build_test.sh b/examples/demo9/golang/build_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..da9c7eaff22baeb1b08c16b43b3743ab14aaf531 --- /dev/null +++ b/examples/demo9/golang/build_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e +set -x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin/demo9 + +docker pull golang +docker run --rm -it -v$PWD:/opt/local/etsi/demo9 golang bash -c "cd /opt/local/etsi/demo9 && ./build-demo9.sh" + +echo "" +echo ">>> Demo Service build completed" diff --git a/examples/demo9/golang/client/README.md b/examples/demo9/golang/client/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5f189fd74fa53494e89c61c88c86a5962364b4e5 --- /dev/null +++ b/examples/demo9/golang/client/README.md @@ -0,0 +1,84 @@ +# Go API client for swagger + +The MEC Sandbox API described using OpenAPI + +## Overview +This API client was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project. By using the [swagger-spec](https://github.com/swagger-api/swagger-spec) from a remote server, you can easily generate an API client. + +- API version: 0.0.9 +- Package version: 1.0.0 +- Build package: io.swagger.codegen.v3.generators.go.GoClientCodegen + +## Installation +Put the package under your project folder and add the following in import: +```golang +import "./swagger" +``` + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*AuthorizationApi* | [**Login**](docs/AuthorizationApi.md#login) | **Post** /login | Initiate OAuth login procedure and creates a MEC Sandbox instance +*AuthorizationApi* | [**Logout**](docs/AuthorizationApi.md#logout) | **Post** /logout | Terminates User Session and delete the Sandbox instance +*SandboxAppInstancesApi* | [**SandboxAppInstancesDELETE**](docs/SandboxAppInstancesApi.md#sandboxappinstancesdelete) | **Delete** /sandboxAppInstances/{app_instance_id} | Delete an existing application instance +*SandboxAppInstancesApi* | [**SandboxAppInstancesGET**](docs/SandboxAppInstancesApi.md#sandboxappinstancesget) | **Get** /sandboxAppInstances | Get the list of the available application instance identifiers +*SandboxAppInstancesApi* | [**SandboxAppInstancesPOST**](docs/SandboxAppInstancesApi.md#sandboxappinstancespost) | **Post** /sandboxAppInstances | Create a new application instance identifier +*SandboxLogsSubscriptionsApi* | [**SandboxLogsSubscriptionsDELETE**](docs/SandboxLogsSubscriptionsApi.md#sandboxlogssubscriptionsdelete) | **Delete** /sandboxLogsSubscriptions/{subscription_reference} | Subscription to receive logs from the sandbox +*SandboxLogsSubscriptionsApi* | [**SandboxLogsSubscriptionsPOST**](docs/SandboxLogsSubscriptionsApi.md#sandboxlogssubscriptionspost) | **Post** /sandboxLogsSubscriptions | Subscription to receive logs from the sandbox +*SandboxMECServicesApi* | [**SandboxMecServicesGET**](docs/SandboxMECServicesApi.md#sandboxmecservicesget) | **Get** /sandboxMecServices | Get the list of the available MEC services +*SandboxNetworkScenariosApi* | [**SandboxIndividualNetworkScenariosGET**](docs/SandboxNetworkScenariosApi.md#sandboxindividualnetworkscenariosget) | **Get** /sandboxNetworkScenarios/{network_scenario_id} | Get description of a Network Scenario to be used. +*SandboxNetworkScenariosApi* | [**SandboxNetworkScenarioDELETE**](docs/SandboxNetworkScenariosApi.md#sandboxnetworkscenariodelete) | **Delete** /sandboxNetworkScenarios/{network_scenario_id} | Deactivate the Network Scenario. +*SandboxNetworkScenariosApi* | [**SandboxNetworkScenarioPOST**](docs/SandboxNetworkScenariosApi.md#sandboxnetworkscenariopost) | **Post** /sandboxNetworkScenarios/{network_scenario_id} | Selects the Network Scenario to be used. +*SandboxNetworkScenariosApi* | [**SandboxNetworkScenariosGET**](docs/SandboxNetworkScenariosApi.md#sandboxnetworkscenariosget) | **Get** /sandboxNetworkScenarios | Get the list of the available network scenarios +*SandboxUEControllerApi* | [**SandboxUeControllerGET**](docs/SandboxUEControllerApi.md#sandboxuecontrollerget) | **Get** /sandboxUeController | Get the list of the available UEs (e.g. \"Stationary UE\") +*SandboxUEControllerApi* | [**SandboxUeControllerPATCH**](docs/SandboxUEControllerApi.md#sandboxuecontrollerpatch) | **Patch** /sandboxUeController/{user_equipment_id}/{user_equipment_value} | set the new value of the UE + +## Documentation For Models + + - [ApplicationInfo](docs/ApplicationInfo.md) + - [CellularDomainConfig](docs/CellularDomainConfig.md) + - [CellularPoaConfig](docs/CellularPoaConfig.md) + - [ConnectivityConfig](docs/ConnectivityConfig.md) + - [CpuConfig](docs/CpuConfig.md) + - [D2dConfig](docs/D2dConfig.md) + - [Deployment](docs/Deployment.md) + - [DnConfig](docs/DnConfig.md) + - [Domain](docs/Domain.md) + - [EgressService](docs/EgressService.md) + - [ExternalConfig](docs/ExternalConfig.md) + - [GeoData](docs/GeoData.md) + - [GpuConfig](docs/GpuConfig.md) + - [IngressService](docs/IngressService.md) + - [LineString](docs/LineString.md) + - [MemoryConfig](docs/MemoryConfig.md) + - [NetworkCharacteristics](docs/NetworkCharacteristics.md) + - [NetworkLocation](docs/NetworkLocation.md) + - [PhysicalLocation](docs/PhysicalLocation.md) + - [Poa4GConfig](docs/Poa4GConfig.md) + - [Poa5GConfig](docs/Poa5GConfig.md) + - [PoaWifiConfig](docs/PoaWifiConfig.md) + - [Point](docs/Point.md) + - [ProblemDetails](docs/ProblemDetails.md) + - [Process](docs/Process.md) + - [Sandbox](docs/Sandbox.md) + - [SandboxAppInstances](docs/SandboxAppInstances.md) + - [SandboxLogsSubscriptions](docs/SandboxLogsSubscriptions.md) + - [SandboxMecServices](docs/SandboxMecServices.md) + - [SandboxNetworkScenario](docs/SandboxNetworkScenario.md) + - [Scenario](docs/Scenario.md) + - [ScenarioConfig](docs/ScenarioConfig.md) + - [ServiceConfig](docs/ServiceConfig.md) + - [ServicePort](docs/ServicePort.md) + - [Ue](docs/Ue.md) + - [Zone](docs/Zone.md) + +## Documentation For Authorization + Endpoints do not require authorization. + + +## Author + +cti_support@etsi.org diff --git a/examples/demo9/golang/client/api/swagger.yaml b/examples/demo9/golang/client/api/swagger.yaml new file mode 100644 index 0000000000000000000000000000000000000000..35426b79d86e2befc8c171070801ddc762e8e5a6 --- /dev/null +++ b/examples/demo9/golang/client/api/swagger.yaml @@ -0,0 +1,1521 @@ +openapi: 3.0.0 +info: + title: MEC Sandbox API + description: The MEC Sandbox API described using OpenAPI + contact: + email: cti_support@etsi.org + license: + name: BSD-3-Clause + url: https://forge.etsi.org/legal-matters + version: 1.0.0 +servers: +- url: http://localhost/sandbox-api/v1 +paths: + /login: + post: + tags: + - Authorization + summary: Initiate OAuth login procedure and creates a MEC Sandbox instance + description: Initiate OAuth login procedure and creates a MEC Sandbox instance + operationId: login + parameters: + - name: provider + in: query + description: Oauth provider + required: true + style: form + explode: true + schema: + type: string + enum: + - GITHUB + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Oauth' + "400": + description: Bad Request + content: {} + "401": + description: Unauthorized + content: {} + "404": + description: Not Found + content: {} + /namespace: + get: + tags: + - Authorization + summary: Get the namespace against the User Code + description: Get the namespace against the User Code + operationId: GetNamespace + parameters: + - name: user_code + in: query + description: User Code obtained from the login endpoint + required: true + style: form + explode: true + schema: + type: string + responses: + "200": + description: This status code indicates that the request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Namespace' + "400": + description: Bad Request + content: {} + "401": + description: Unauthorized + content: {} + "404": + description: Not Found + content: {} + /logout: + post: + tags: + - Authorization + summary: Terminates User Session and delete the Sandbox instance + description: Terminates User Session and delete the Sandbox instance + operationId: logout + parameters: + - name: sandbox_name + in: query + description: Sandbox identifier + required: true + style: form + explode: true + schema: + type: string + responses: + "200": + description: OK + content: {} + "400": + description: Bad Request + content: {} + "401": + description: Unauthorized + content: {} + "404": + description: Not Found + content: {} + /sandboxNetworkScenarios: + get: + tags: + - Sandbox Network Scenarios + summary: Get the list of the available network scenarios + description: This method retrieves the list of the available network scenarios. + operationId: SandboxNetworkScenariosGET + parameters: + - name: sandbox_name + in: query + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + responses: + "200": + description: "Upon success, a response message content containing an array\ + \ of the list of the available network scenarios." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SandboxNetworkScenario' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No network scenario found." + /sandboxNetworkScenarios/{sandbox_name}: + get: + tags: + - Sandbox Network Scenarios + summary: Get description of a Network Scenario to be used. + description: This method retrive description of a the network scenario + operationId: SandboxIndividualNetworkScenariosGET + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + - name: network_scenario_id + in: query + description: Network scenario to retrieve + required: true + style: simple + explode: false + schema: + type: string + enum: + - 4g-5g-macro-v2x + - 4g-5g-macro-v2x-fed + - 4g-5g-wifi-macro + - 4g-macro + - 4g-wifi-macro + - dual-mep-4g-5g-wifi-macro + - dual-mep-short-path + x-exportParamName: Provider + x-optionalDataType: String + responses: + "200": + description: "Upon success, an empty response message." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Scenario' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : used when a client provided a URI that cannot\ + \ be mapped to a valid resource URI." + post: + tags: + - Sandbox Network Scenarios + summary: Selects the Network Scenario to be activated. + description: This method selects the network scenario to be activated. This + request initiates the creation of necessary MEC services for specific network + scenario + operationId: SandboxNetworkScenarioPOST + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + - name: network_scenario_id + in: query + description: Network scenario to be used + required: true + style: simple + explode: false + schema: + type: string + enum: + - 4g-5g-macro-v2x + - 4g-5g-macro-v2x-fed + - 4g-5g-wifi-macro + - 4g-macro + - 4g-wifi-macro + - dual-mep-4g-5g-wifi-macro + - dual-mep-short-path + x-exportParamName: Provider + x-optionalDataType: String + responses: + "201": + description: "Upon success, an empty response message." + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : used when a client provided a URI that cannot\ + \ be mapped to a valid resource URI." + /sandboxNetworkScenarios/{sandbox_name}/{network_scenario_id}: + delete: + tags: + - Sandbox Network Scenarios + summary: Deactivate the Network Scenario. + description: This method deactivates the network scenario + operationId: SandboxNetworkScenarioDELETE + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + - name: network_scenario_id + in: path + description: Network scenario to be used + required: true + style: simple + explode: false + schema: + type: string + enum: + - 4g-5g-macro-v2x + - 4g-5g-macro-v2x-fed + - 4g-5g-wifi-macro + - 4g-macro + - 4g-wifi-macro + - dual-mep-4g-5g-wifi-macro + - dual-mep-short-path + x-exportParamName: Provider + x-optionalDataType: String + responses: + "204": + description: "Upon success, an empty response message." + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : used when a client provided a URI that cannot\ + \ be mapped to a valid resource URI." + /apiConsoleLogs/{sandbox_name}: + get: + tags: + - API Console + summary: Get the list of http logs in the activated scenario. + description: Get the list of http logs in the activated scenario. + operationId: ApiConsoleLogsGET + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + responses: + "200": + description: "Upon success, a response message content containing an array of the list of the HTTP logs." + content: + application/json: + schema: + type: array + items: + type: object + description: Object representing the HTTP logs. + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No Activated Scenario found against the provided sandbox." + /sandboxUeController/{sandbox_name}: + get: + tags: + - Sandbox UE Controller + summary: Get the list of the available UEs (e.g. "Stationary UE") + description: This method retrieves the list of the available available UEs. + operationId: SandboxUeControllerGET + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + responses: + "200": + description: "Upon success, a response message content containing an array\ + \ of the list of the available UEs." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UE' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No UE found." + patch: + tags: + - Sandbox UE Controller + summary: set the new value of the UE + description: This method sets the new value of the UE. + operationId: SandboxUeControllerPATCH + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + - name: user_equipment_id + in: query + description: User equipmenet identifier + required: true + style: simple + explode: false + schema: + type: string + enum: + - Stationary_UE + - Low_Velocity_UE + - High_Velocity_UE + - name: user_equipment_value + in: query + description: It uniquely identifies a UE to set the new value + required: true + style: simple + explode: true + schema: + type: integer + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + responses: + "200": + description: "Upon success, an empty response message." + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No UE found." + /sandboxMecServices/{sandbox_name}: + get: + tags: + - Sandbox MEC Services + summary: Get the list of the available MEC services + description: This method retrieves the list of the available MEC services. + operationId: SandboxMecServicesGET + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + responses: + "200": + description: "Upon success, a response message content containing an array\ + \ of the list of the available MEC services." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SandboxMecServices' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No MEC services found." + /sandboxAppInstances/{sandbox_name}: + get: + tags: + - Sandbox App Instances + summary: Get the list of the available application instance identifiers + description: This method retrieves the list of the available application instance + identifiers + operationId: SandboxAppInstancesGET + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + responses: + "200": + description: "Upon success, a response message content containing an array\ + \ of the list of the available application instance identifier." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ApplicationInfo' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No application instance identifier found." + post: + tags: + - Sandbox App Instances + summary: Create a new application instance identifier + description: This method creates a new application instance + operationId: SandboxAppInstancesPOST + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + requestBody: + description: Pet to add to the store + content: + application/json: + schema: + $ref: '#/components/schemas/ApplicationInfo' + required: true + responses: + "201": + description: "Upon success, a response message content containing an array\ + \ of the list of the available application instance identifier." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ApplicationInfo' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No application instance identifier found." + /sandboxAppInstances/{sandbox_name}/{app_instance_id}: + delete: + tags: + - Sandbox App Instances + summary: Delete an existing application instance + description: This method removes an existing application instance + operationId: SandboxAppInstancesDELETE + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + - name: app_instance_id + in: path + description: It uniquely identifies a MEC application instance identifier + required: true + style: simple + explode: false + schema: + type: string + responses: + "204": + description: No content. + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : No application instance identifier found." + /sandboxLogsSubscriptions/{sandbox_name}: + post: + tags: + - Sandbox Logs Subscriptions + summary: Subscription to receive logs from the sandbox + description: This method is used to receive logs from the sandbox. + operationId: SandboxLogsSubscriptionsPOST + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + responses: + "201": + description: "Upon success, a response message content containing the subscription\ + \ reference." + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SandboxLogsSubscriptions' + x-content-type: application/json + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + /sandboxLogsSubscriptions/{sandbox_name}/{subscription_reference}: + delete: + tags: + - Sandbox Logs Subscriptions + summary: Subscription to receive logs from the sandbox + description: This method is used to receive logs from the sandbox. + operationId: SandboxLogsSubscriptionsDELETE + parameters: + - name: sandbox_name + in: path + description: Sandbox identifier + required: true + style: simple + explode: true + schema: + type: string + - name: subscription_reference + in: path + description: It uniquely identifies subscription reference to receive logs + from the sandbox + required: true + style: simple + explode: false + schema: + type: string + responses: + "204": + description: "Upon success, an empty response message." + "400": + description: "Bad Request : used to indicate that incorrect parameters were\ + \ passed to the request." + "401": + description: "Unauthorized : used when the client did not submit credentials." + "404": + description: "Not Found : Subscription reference not found." +components: + schemas: + SandboxNetworkScenario: + title: SandboxNetworkScenario + required: + - id + type: object + properties: + id: + type: string + description: The network scenario name. + example: "[\"4g-5g-macro\"]" + example: + id: "[\"4g-5g-macro\"]" + Scenario: + type: object + properties: + version: + type: string + description: Scenario version + id: + type: string + description: Unique scenario ID + name: + type: string + description: Unique scenario name + description: + type: string + description: User description of the scenario. + config: + $ref: '#/components/schemas/ScenarioConfig' + deployment: + $ref: '#/components/schemas/Deployment' + description: Scenario object + example: {} + ScenarioConfig: + type: object + properties: + visualization: + type: string + description: Visualization configuration + other: + type: string + description: Other scenario configuration + description: Scenario configuration + example: + visualization: visualization + other: other + Deployment: + type: object + properties: + netChar: + $ref: '#/components/schemas/NetworkCharacteristics' + connectivity: + $ref: '#/components/schemas/ConnectivityConfig' + d2d: + $ref: '#/components/schemas/D2dConfig' + interDomainLatency: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latency" + interDomainLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation" + interDomainThroughput: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl\ + \ and throughputDl" + interDomainPacketLoss: + type: number + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss" + format: double + meta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + userMeta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + domains: + type: array + items: + $ref: '#/components/schemas/Domain' + description: Network deployment object + example: {} + NetworkCharacteristics: + type: object + properties: + latency: + type: integer + description: Latency in ms + latencyVariation: + type: integer + description: Latency variation in ms + latencyDistribution: + type: string + description: "Latency distribution. Can only be set in the Scenario Deployment\ + \ network characteristics, ignored otherwise. Latency distribution is\ + \ set for the whole network and applied to every end-to-end traffic flows.\ + \ Default value is 'Normal' distribution." + enum: + - Normal + - Pareto + - Paretonormal + - Uniform + throughput: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by throughputUl\ + \ and throughputDl" + throughputDl: + type: integer + description: Downlink throughput limit in Mbps + throughputUl: + type: integer + description: Uplink throughput limit in Mbps + packetLoss: + type: number + description: Packet loss percentage + format: double + description: Network characteristics object + example: {} + ConnectivityConfig: + type: object + properties: + model: + type: string + description: "Connectivity Model:
  • OPEN: Any node in the scenario can\ + \ communicate with any node
  • PDU: Terminal nodes (UE) require a PDU\ + \ session to the target DN" + enum: + - OPEN + - PDU + example: {} + D2dConfig: + type: object + properties: + d2dMaxDistance: + type: number + description: Maximum distance for D2D. Default distance is 100m + disableD2dViaNetwork: + type: boolean + description: Enable-Disable D2D via network. Default value is false + description: D2D config + Domain: + type: object + properties: + id: + type: string + description: Unique domain ID + name: + type: string + description: Domain name + type: + type: string + description: Domain type + enum: + - OPERATOR + - OPERATOR-CELLULAR + - PUBLIC + netChar: + $ref: '#/components/schemas/NetworkCharacteristics' + interZoneLatency: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latency" + interZoneLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation" + interZoneThroughput: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl\ + \ and throughputDl" + interZonePacketLoss: + type: number + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss" + format: double + meta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + userMeta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + cellularDomainConfig: + $ref: '#/components/schemas/CellularDomainConfig' + zones: + type: array + items: + $ref: '#/components/schemas/Zone' + description: Operator domain object + example: {} + CellularDomainConfig: + type: object + properties: + mnc: + type: string + description: Mobile Network Code part of PLMN identity as defined in ETSI + TS 136 413 + mcc: + type: string + description: Mobile Country Code part of PLMN identity as defined in ETSI + TS 136 413 + defaultCellId: + type: string + description: The E-UTRAN Cell Identity as defined in ETSI TS 136 413 if + no cellId is defined for the cell or if not applicable + description: Cellular domain configuration information + example: {} + Zone: + type: object + properties: + id: + type: string + description: Unique zone ID + name: + type: string + description: Zone name + type: + type: string + description: Zone type + enum: + - ZONE + - COMMON + netChar: + $ref: '#/components/schemas/NetworkCharacteristics' + interFogLatency: + type: integer + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + interFogLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + interFogThroughput: + type: integer + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + interFogPacketLoss: + type: number + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + format: double + interEdgeLatency: + type: integer + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + interEdgeLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + interEdgeThroughput: + type: integer + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + interEdgePacketLoss: + type: number + description: "**DEPRECATED** As of release 1.3.0, no longer supported" + format: double + edgeFogLatency: + type: integer + description: "**DEPRECATED** As of release 1.3.0, replaced by netChar latency" + edgeFogLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.3.0, replaced by netChar latencyVariation" + edgeFogThroughput: + type: integer + description: "**DEPRECATED** As of release 1.3.0, replaced by netChar throughput" + edgeFogPacketLoss: + type: number + description: "**DEPRECATED** As of release 1.3.0, replaced by netChar packetLoss" + format: double + meta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + userMeta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + networkLocations: + type: array + items: + $ref: '#/components/schemas/NetworkLocation' + description: Logical zone (MEC network) object + example: {} + NetworkLocation: + type: object + properties: + id: + type: string + description: Unique network location ID + name: + type: string + description: Network location name + type: + type: string + description: Network location type + enum: + - POA + - POA-4G + - POA-5G + - POA-WIFI + - DEFAULT + netChar: + $ref: '#/components/schemas/NetworkCharacteristics' + terminalLinkLatency: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latency" + terminalLinkLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation" + terminalLinkThroughput: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl\ + \ and throughputDl" + terminalLinkPacketLoss: + type: number + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss" + format: double + meta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + userMeta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + cellularPoaConfig: + $ref: '#/components/schemas/CellularPoaConfig' + poa4GConfig: + $ref: '#/components/schemas/Poa4GConfig' + poa5GConfig: + $ref: '#/components/schemas/Poa5GConfig' + poaWifiConfig: + $ref: '#/components/schemas/PoaWifiConfig' + geoData: + $ref: '#/components/schemas/GeoData' + physicalLocations: + type: array + items: + $ref: '#/components/schemas/PhysicalLocation' + description: Logical network location object + example: {} + CellularPoaConfig: + type: object + properties: + cellId: + type: string + description: The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including + the ID of the eNB serving the cell + description: "**DEPRECATED** As of release 1.5.1, renamed to poa4GConfig" + Poa4GConfig: + type: object + properties: + cellId: + type: string + description: The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including + the ID of the eNB serving the cell + description: Cellular 4G POA configuration information + Poa5GConfig: + type: object + properties: + cellId: + type: string + description: The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including + the ID of the NR serving the cell + description: Cellular 5G POA configuration information + PoaWifiConfig: + type: object + properties: + macId: + type: string + description: WIFI POA MAC Address + description: WIFI POA configuration information + GeoData: + type: object + properties: + location: + $ref: '#/components/schemas/Point' + radius: + type: number + description: Optional - Radius (in meters) around the location + path: + $ref: '#/components/schemas/LineString' + eopMode: + type: string + description: "End-of-Path mode:
  • LOOP: When path endpoint is reached,\ + \ start over from the beginning
  • REVERSE: When path endpoint is reached,\ + \ return on the reverse path" + enum: + - LOOP + - REVERSE + velocity: + type: number + description: Speed of movement along path in m/s + d2dInRange: + type: array + items: + type: string + description: D2D UEs visible to UE + poaInRange: + type: array + items: + type: string + description: POAs visible to UE + description: Geographic data + Point: + required: + - type + type: object + properties: + type: + type: string + description: Must be Point + enum: + - Point + coordinates: + type: array + description: "For a Point, coordinates MUST be an array of two decimal numbers;\ + \ longitude and latitude precisely in that order" + items: + type: number + description: A single position in coordinate space (GeoJSON); a position is + an array of two numbers + externalDocs: + url: https://tools.ietf.org/html/rfc7946 + LineString: + required: + - type + type: object + properties: + type: + type: string + description: Must be LineString + enum: + - LineString + coordinates: + type: array + description: "For a LineString, coordinates is an array of two or more positions;\ + \ a position is an array of two decimal numbers (longitude and latitude\ + \ precisely in that order)" + items: + type: array + items: + type: number + description: An array of two or more positions in coordinate space (GeoJSON); + a position is an array of two numbers + externalDocs: + url: https://tools.ietf.org/html/rfc7946 + PhysicalLocation: + type: object + properties: + id: + type: string + description: Unique physical location ID + name: + type: string + description: Physical location name + type: + type: string + description: Physical location type + enum: + - UE + - FOG + - EDGE + - CN + - DC + isExternal: + type: boolean + description: |- + true: Physical location is external to MEEP + false: Physical location is internal to MEEP + geoData: + $ref: '#/components/schemas/GeoData' + networkLocationsInRange: + type: array + items: + type: string + description: Names of network locations within range of physical location + connected: + type: boolean + description: |- + true: Physical location has network connectivity + false: Physical location has no network connectivity + wireless: + type: boolean + description: |- + true: Physical location uses a wireless connection + false: Physical location uses a wired connection + wirelessType: + type: string + description: |- + Prioritized, comma-separated list of supported wireless connection types. + Default priority if not specififed is 'wifi,5g,4g,other'. + Wireless connection types: + - 4g + - 5g + - wifi + - other + dataNetwork: + $ref: '#/components/schemas/DNConfig' + meta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + userMeta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + processes: + type: array + items: + $ref: '#/components/schemas/Process' + netChar: + $ref: '#/components/schemas/NetworkCharacteristics' + linkLatency: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latency" + linkLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation" + linkThroughput: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl\ + \ and throughputDl" + linkPacketLoss: + type: number + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss" + format: double + macId: + type: string + description: Physical location MAC Address + description: Physical location object + example: {} + DNConfig: + type: object + properties: + dnn: + type: string + description: Data Network Name + ladn: + type: boolean + description: |- + true: Data network serves local area only + false: Data network is not limited to local area + ecsp: + type: string + description: Edge Compute Service Provider + description: Data Network Configuration + example: {} + Process: + type: object + properties: + id: + type: string + description: Unique process ID + name: + type: string + description: Process name + type: + type: string + description: Process type + enum: + - UE-APP + - EDGE-APP + - MEC-SVC + - CLOUD-APP + isExternal: + type: boolean + description: |- + true: process is external to MEEP + false: process is internal to MEEP + image: + type: string + description: Docker image to deploy inside MEEP + environment: + type: string + description: "Environment variables using the format NAME=\"value\",NAME=\"\ + value\",NAME=\"value\"" + commandArguments: + type: string + description: Arguments to command executable + commandExe: + type: string + description: Executable to invoke at container start up + serviceConfig: + $ref: '#/components/schemas/ServiceConfig' + gpuConfig: + $ref: '#/components/schemas/GpuConfig' + memoryConfig: + $ref: '#/components/schemas/MemoryConfig' + cpuConfig: + $ref: '#/components/schemas/CpuConfig' + externalConfig: + $ref: '#/components/schemas/ExternalConfig' + status: + type: string + description: Process status + userChartLocation: + type: string + description: Chart location for the deployment of the chart provided by + the user + userChartAlternateValues: + type: string + description: Chart values.yaml file location for the deployment of the chart + provided by the user + userChartGroup: + type: string + description: Chart supplemental information related to the group (service) + meta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + userMeta: + type: object + additionalProperties: + type: string + description: "Key/Value Pair Map (string, string)" + netChar: + $ref: '#/components/schemas/NetworkCharacteristics' + appLatency: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latency" + appLatencyVariation: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation" + appThroughput: + type: integer + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl\ + \ and throughputDl" + appPacketLoss: + type: number + description: "**DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss" + format: double + placementId: + type: string + description: Identifier used for process placement in AdvantEDGE cluster + description: Application or service object + example: {} + ServiceConfig: + type: object + properties: + name: + type: string + description: Unique service name + meSvcName: + type: string + description: "Multi-Edge service name, if any" + ports: + type: array + items: + $ref: '#/components/schemas/ServicePort' + description: Service object + example: {} + ServicePort: + type: object + properties: + protocol: + type: string + description: Protocol that the application is using (TCP or UDP) + port: + type: integer + description: Port number that the service is listening on + externalPort: + type: integer + description: | + External port number on which to expose the application (30000 - 32767)
  • Only one application allowed per external port
  • Scenario builder must configure to prevent conflicts + description: Service port object + example: {} + GpuConfig: + type: object + properties: + type: + type: string + description: Requested GPU type + count: + type: integer + description: Number of GPUs requested + description: GPU configuration object + MemoryConfig: + type: object + properties: + min: + type: integer + description: Minimum requested memory + max: + type: integer + description: Maximum requested memory + description: Memory configuration object + CpuConfig: + type: object + properties: + min: + type: number + description: Minimum requested CPU + format: float + max: + type: number + description: Maximum requested CPU + format: float + description: CPU configuration object + ExternalConfig: + type: object + properties: + ingressServiceMap: + type: array + items: + $ref: '#/components/schemas/IngressService' + egressServiceMap: + type: array + items: + $ref: '#/components/schemas/EgressService' + description: |- + External Process configuration. + NOTE: Only valid if 'isExternal' is set. + example: {} + IngressService: + type: object + properties: + name: + type: string + description: Service name (unique or multi-edge) + port: + type: integer + description: Internal service port number + externalPort: + type: integer + description: Externally-exposed unique service port in range (30000 - 32767) + protocol: + type: string + description: Service protocol (TCP or UDP) + description: Internal service exposed externally via specific port + EgressService: + type: object + properties: + name: + type: string + description: Service name + meSvcName: + type: string + description: "Multi-Edge service name, if any" + ip: + type: string + description: External node IP address + port: + type: integer + description: Service port number + protocol: + type: string + description: Service protocol (TCP or UDP) + description: External service exposed internally via specific port + UE: + title: UE + required: + - id + type: object + properties: + id: + type: string + description: The UE name. + example: "[\"Stationary UE\"]" + count: + type: integer + description: The number of UE instance. + example: + id: "[\"Stationary UE\"]" + SandboxMecServices: + title: SandboxMecServices + required: + - id + type: object + properties: + id: + type: string + description: The MEC service name. + example: "[\"Location (030)\"]" + service_id: + type: string + description: "When a MEC service is selected, this field contains a token\ + \ which shall be used in MEC service API URI." + example: + service_id: service_id + id: "[\"Location (030)\"]" + SandboxAppInstances: + title: SandboxAppInstances + required: + - id + type: object + properties: + id: + type: string + description: The application instance identifier. + example: "[\"c5f834ae-db0c-4eec-bdfe-3dfc55f91b4a\"]" + example: + id: "[\"c5f834ae-db0c-4eec-bdfe-3dfc55f91b4a\"]" + SandboxLogsSubscriptions: + title: SandboxLogsSubscriptions + required: + - callbackReference + type: object + properties: + callbackReference: + type: string + description: The callback to notify log messages. + example: "[\"http://my.callback.com/sandbox/logs/some-id\"]" + subscriptionReference: + type: string + description: The reference of the subscription. + example: "[\"37dd7ef4-c382-11ee-9301-5fe5aa3a97cf\"]" + example: + subscriptionReference: "[\"37dd7ef4-c382-11ee-9301-5fe5aa3a97cf\"]" + callbackReference: "[\"http://my.callback.com/sandbox/logs/some-id\"]" + Oauth: + type: object + properties: + user_code: + type: string + description: User code from the authentication provider + x-etsi-mec-cardinality: "1" + x-etsi-mec-origin-type: String + verification_uri: + type: string + description: Verification URI to go to and enter the user code in order + to authenticate + x-etsi-mec-cardinality: "1" + x-etsi-mec-origin-type: String + description: Sandbox object + example: {} + Namespace: + type: object + properties: + sandbox_name: + type: string + description: Name of the Sandbox + x-etsi-mec-cardinality: "1" + x-etsi-mec-origin-type: String + description: Namespace object + example: {} + ProblemDetails: + required: + - detail + - status + type: object + properties: + type: + type: string + description: | + A URI reference according to IETF RFC 3986 that identifies the problem type. It is encouraged that the URI provides human-readable documentation for the problem (e.g. using HTML) when dereferenced. When this member is not present, its value is assumed to be "about:blank". + format: URI + title: + type: string + description: | + A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization. If type is given and other than "about:blank", this attribute shall also be provided. A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4). + status: + type: integer + description: | + The HTTP status code for this occurrence of the problem. The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. + detail: + type: string + description: | + A human-readable explanation specific to this occurrence of the problem. + instance: + type: string + description: | + A URI reference that identifies the specific occurrence of the problem. It may yield further information if dereferenced. + format: URI + description: | + The definition of the general "ProblemDetails" data structure from IETF RFC 7807 is reproduced inthis structure. Compared to the general framework defined in IETF RFC 7807, the "status" and "detail" attributes are mandated to be included by the present document, to ensure that the response contains additional textual information about an error. IETF RFC 7807 foresees extensibility of the "ProblemDetails" type. It is possible that particular APIs in the present document, or particular implementations, define extensions to define additional attributes that provide more information about the error. The description column only provides some explanation of the meaning to Facilitate understanding of the design. For a full description, see IETF RFC 7807. + example: + instance: instance + detail: detail + type: type + title: title + status: 0 + ApplicationInfo: + required: + - name + - nodeName + type: object + properties: + id: + type: string + description: Application Instance UUID + name: + type: string + description: Application name + nodeName: + type: string + description: Name of node where application instance is running + type: + type: string + description: Application Type + enum: + - USER + - SYSTEM + persist: + type: boolean + description: Reserved for internal platform usage + description: MEC Application instance information + example: + id: 00afec52-f0b6-464e-a660-33568c0975b9 + name: MyAppName + nodeName: node1 + type: USER diff --git a/examples/demo9/golang/client/api_authorization.go b/examples/demo9/golang/client/api_authorization.go new file mode 100644 index 0000000000000000000000000000000000000000..538f8ff61d33b34f695f71c990c8bd4d3cf2089f --- /dev/null +++ b/examples/demo9/golang/client/api_authorization.go @@ -0,0 +1,264 @@ + +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// Linger please +var ( + _ context.Context +) + +type AuthorizationApiService service +/* +AuthorizationApiService Get the namespace against the User Code +Get the namespace against the User Code + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param userCode User Code obtained from the login endpoint + +*/ +func (a *AuthorizationApiService) GetNamespace(ctx context.Context, userCode string) (Namespace, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue Namespace + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/namespace" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("user_code", parameterToString(userCode, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 200 { + var v Namespace + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} +/* +AuthorizationApiService Initiate OAuth login procedure and creates a MEC Sandbox instance +Initiate OAuth login procedure and creates a MEC Sandbox instance + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param provider Oauth provider +@return Sandbox +*/ +func (a *AuthorizationApiService) Login(ctx context.Context, provider string) (Oauth, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Post") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue Oauth + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/login" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("provider", parameterToString(provider, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 201 { + var v Oauth + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} +/* +AuthorizationApiService Terminates User Session and delete the Sandbox instance +Terminates User Session and delete the Sandbox instance + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + +*/ +func (a *AuthorizationApiService) Logout(ctx context.Context, sandboxName string) (*http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Post") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/logout" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("sandbox_name", parameterToString(sandboxName, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarHttpResponse, err + } + + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + return localVarHttpResponse, newErr + } + + return localVarHttpResponse, nil +} diff --git a/examples/demo9/golang/client/api_sandbox_app_instances.go b/examples/demo9/golang/client/api_sandbox_app_instances.go new file mode 100644 index 0000000000000000000000000000000000000000..57bd0794a94279707142d48b95daef1dae69f4d8 --- /dev/null +++ b/examples/demo9/golang/client/api_sandbox_app_instances.go @@ -0,0 +1,270 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// Linger please +var ( + _ context.Context +) + +type SandboxAppInstancesApiService service + +/* +SandboxAppInstancesApiService Delete an existing application instance +This method removes an existing application instance + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + * @param appInstanceId It uniquely identifies a MEC application instance identifier + +*/ +func (a *SandboxAppInstancesApiService) SandboxAppInstancesDELETE(ctx context.Context, sandboxName string, appInstanceId string) (*http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Delete") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxAppInstances/{sandbox_name}/{app_instance_id}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + localVarPath = strings.Replace(localVarPath, "{"+"app_instance_id"+"}", fmt.Sprintf("%v", appInstanceId), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + return localVarHttpResponse, newErr + } + + return localVarHttpResponse, nil +} + +/* +SandboxAppInstancesApiService Get the list of the available application instance identifiers +This method retrieves the list of the available application instance identifiers + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier +@return []ApplicationInfo +*/ +func (a *SandboxAppInstancesApiService) SandboxAppInstancesGET(ctx context.Context, sandboxName string) ([]ApplicationInfo, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []ApplicationInfo + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxAppInstances/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 200 { + var v []ApplicationInfo + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} + +/* +SandboxAppInstancesApiService Create a new application instance identifier +This method creates a new application instance + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param body Pet to add to the store + * @param sandboxName Sandbox identifier +@return []ApplicationInfo +*/ +func (a *SandboxAppInstancesApiService) SandboxAppInstancesPOST(ctx context.Context, body ApplicationInfo, sandboxName string) ([]ApplicationInfo, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Post") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []ApplicationInfo + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxAppInstances/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + // body params + localVarPostBody = &body + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 201 { + var v []ApplicationInfo + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} diff --git a/examples/demo9/golang/client/api_sandbox_logs_subscriptions.go b/examples/demo9/golang/client/api_sandbox_logs_subscriptions.go new file mode 100644 index 0000000000000000000000000000000000000000..5aeca5c0ece4e36c451c41b8abd7248dd63a2b6d --- /dev/null +++ b/examples/demo9/golang/client/api_sandbox_logs_subscriptions.go @@ -0,0 +1,182 @@ + +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + "strings" + "fmt" +) + +// Linger please +var ( + _ context.Context +) + +type SandboxLogsSubscriptionsApiService service +/* +SandboxLogsSubscriptionsApiService Subscription to receive logs from the sandbox +This method is used to receive logs from the sandbox. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + * @param subscriptionReference It uniquely identifies subscription reference to receive logs from the sandbox + +*/ +func (a *SandboxLogsSubscriptionsApiService) SandboxLogsSubscriptionsDELETE(ctx context.Context, sandboxName string, subscriptionReference string) (*http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Delete") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxLogsSubscriptions/{sandbox_name}/{subscription_reference}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + localVarPath = strings.Replace(localVarPath, "{"+"subscription_reference"+"}", fmt.Sprintf("%v", subscriptionReference), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarHttpResponse, err + } + + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + return localVarHttpResponse, newErr + } + + return localVarHttpResponse, nil +} +/* +SandboxLogsSubscriptionsApiService Subscription to receive logs from the sandbox +This method is used to receive logs from the sandbox. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier +@return []SandboxLogsSubscriptions +*/ +func (a *SandboxLogsSubscriptionsApiService) SandboxLogsSubscriptionsPOST(ctx context.Context, sandboxName string) ([]SandboxLogsSubscriptions, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Post") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []SandboxLogsSubscriptions + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxLogsSubscriptions/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 201 { + var v []SandboxLogsSubscriptions + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} diff --git a/examples/demo9/golang/client/api_sandbox_mec_services.go b/examples/demo9/golang/client/api_sandbox_mec_services.go new file mode 100644 index 0000000000000000000000000000000000000000..b396e6b436f46d6e363d69e8cf1d6853c9194f17 --- /dev/null +++ b/examples/demo9/golang/client/api_sandbox_mec_services.go @@ -0,0 +1,112 @@ + +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + "strings" + "fmt" +) + +// Linger please +var ( + _ context.Context +) + +type SandboxMECServicesApiService service +/* +SandboxMECServicesApiService Get the list of the available MEC services +This method retrieves the list of the available MEC services. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier +@return []SandboxMecServices +*/ +func (a *SandboxMECServicesApiService) SandboxMecServicesGET(ctx context.Context, sandboxName string) ([]SandboxMecServices, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []SandboxMecServices + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxMecServices/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 200 { + var v []SandboxMecServices + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} diff --git a/examples/demo9/golang/client/api_sandbox_network_scenarios.go b/examples/demo9/golang/client/api_sandbox_network_scenarios.go new file mode 100644 index 0000000000000000000000000000000000000000..724576071ef273a58019a8620d7350e48f14a81d --- /dev/null +++ b/examples/demo9/golang/client/api_sandbox_network_scenarios.go @@ -0,0 +1,344 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// Linger please +var ( + _ context.Context +) + +type SandboxNetworkScenariosApiService service + +/* +SandboxNetworkScenariosApiService Get description of a Network Scenario to be used. +This method retrive description of a the network scenario + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + * @param networkScenarioId Network scenario to retrieve +@return []Scenario +*/ +func (a *SandboxNetworkScenariosApiService) SandboxIndividualNetworkScenariosGET(ctx context.Context, sandboxName string, networkScenarioId string) ([]Scenario, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []Scenario + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxNetworkScenarios/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("network_scenario_id", parameterToString(networkScenarioId, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 200 { + var v []Scenario + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} + +/* +SandboxNetworkScenariosApiService Deactivate the Network Scenario. +This method deactivates the network scenario + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + * @param networkScenarioId Network scenario to be used + +*/ +func (a *SandboxNetworkScenariosApiService) SandboxNetworkScenarioDELETE(ctx context.Context, sandboxName string, networkScenarioId string) (*http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Delete") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxNetworkScenarios/{sandbox_name}/{network_scenario_id}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + localVarPath = strings.Replace(localVarPath, "{"+"network_scenario_id"+"}", fmt.Sprintf("%v", networkScenarioId), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + //fmt.Println("SandboxNetworkScenarioDELETE: r: ", r) + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarHttpResponse, err + } + //fmt.Println("SandboxNetworkScenarioDELETE: localVarHttpResponse: ", localVarHttpResponse) + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + return localVarHttpResponse, newErr + } + + return localVarHttpResponse, nil +} + +/* +SandboxNetworkScenariosApiService Selects the Network Scenario to be activated. +This method selects the network scenario to be activated. This request initiates the creation of necessary MEC services for specific network scenario + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + * @param networkScenarioId Network scenario to be used + +*/ +func (a *SandboxNetworkScenariosApiService) SandboxNetworkScenarioPOST(ctx context.Context, sandboxName string, networkScenarioId string) (*http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Post") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxNetworkScenarios/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("network_scenario_id", parameterToString(networkScenarioId, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + //fmt.Println("SandboxNetworkScenarioPOST: r: ", r) + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarHttpResponse, err + } + //fmt.Println("SandboxNetworkScenarioPOST: localVarHttpResponse: ", localVarHttpResponse) + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + return localVarHttpResponse, newErr + } + + return localVarHttpResponse, nil +} + +/* +SandboxNetworkScenariosApiService Get the list of the available network scenarios +This method retrieves the list of the available network scenarios. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier +@return []SandboxNetworkScenario +*/ +func (a *SandboxNetworkScenariosApiService) SandboxNetworkScenariosGET(ctx context.Context, sandboxName string) ([]SandboxNetworkScenario, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []SandboxNetworkScenario + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxNetworkScenarios" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("sandbox_name", parameterToString(sandboxName, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + //fmt.Println("SandboxNetworkScenariosGET: r: ", r) + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + //fmt.Println("SandboxNetworkScenariosGET: localVarHttpResponse: ", localVarHttpResponse) + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 200 { + var v []SandboxNetworkScenario + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} diff --git a/examples/demo9/golang/client/api_sandbox_ue_controller.go b/examples/demo9/golang/client/api_sandbox_ue_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..1368576b92859f5231260cb932e6aa3ec2d31632 --- /dev/null +++ b/examples/demo9/golang/client/api_sandbox_ue_controller.go @@ -0,0 +1,184 @@ + +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + "strings" + "fmt" +) + +// Linger please +var ( + _ context.Context +) + +type SandboxUEControllerApiService service +/* +SandboxUEControllerApiService Get the list of the available UEs (e.g. \"Stationary UE\") +This method retrieves the list of the available available UEs. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier +@return []Ue +*/ +func (a *SandboxUEControllerApiService) SandboxUeControllerGET(ctx context.Context, sandboxName string) ([]Ue, *http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Get") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []Ue + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxUeController/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarReturnValue, localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHttpResponse, err + } + + if localVarHttpResponse.StatusCode < 300 { + // If we succeed, return the data, otherwise pass on to decode error. + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err == nil { + return localVarReturnValue, localVarHttpResponse, err + } + } + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + if localVarHttpResponse.StatusCode == 200 { + var v []Ue + err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")); + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHttpResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHttpResponse, newErr + } + return localVarReturnValue, localVarHttpResponse, newErr + } + + return localVarReturnValue, localVarHttpResponse, nil +} +/* +SandboxUEControllerApiService set the new value of the UE +This method sets the new value of the UE. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param sandboxName Sandbox identifier + * @param userEquipmentId User equipmenet identifier + * @param userEquipmentValue It uniquely identifies a UE to set the new value + +*/ +func (a *SandboxUEControllerApiService) SandboxUeControllerPATCH(ctx context.Context, sandboxName string, userEquipmentId string, userEquipmentValue int32) (*http.Response, error) { + var ( + localVarHttpMethod = strings.ToUpper("Patch") + localVarPostBody interface{} + localVarFileName string + localVarFileBytes []byte + + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/sandboxUeController/{sandbox_name}" + localVarPath = strings.Replace(localVarPath, "{"+"sandbox_name"+"}", fmt.Sprintf("%v", sandboxName), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + localVarQueryParams.Add("user_equipment_id", parameterToString(userEquipmentId, "")) + localVarQueryParams.Add("user_equipment_value", parameterToString(userEquipmentValue, "")) + // to determine the Content-Type header + localVarHttpContentTypes := []string{} + + // set Content-Type header + localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + + // to determine the Accept header + localVarHttpHeaderAccepts := []string{} + + // set Accept header + localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHttpResponse, err := a.client.callAPI(r) + if err != nil || localVarHttpResponse == nil { + return localVarHttpResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body) + localVarHttpResponse.Body.Close() + if err != nil { + return localVarHttpResponse, err + } + + + if localVarHttpResponse.StatusCode >= 300 { + newErr := GenericSwaggerError{ + body: localVarBody, + error: localVarHttpResponse.Status, + } + return localVarHttpResponse, newErr + } + + return localVarHttpResponse, nil +} diff --git a/examples/demo9/golang/client/client.go b/examples/demo9/golang/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..690d03e5eb6cc4ea03cbfa42eb84daca011827d4 --- /dev/null +++ b/examples/demo9/golang/client/client.go @@ -0,0 +1,490 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "bytes" + "context" + //"crypto/tls" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "time" + "unicode/utf8" + + "golang.org/x/oauth2" +) + +var ( + jsonCheck = regexp.MustCompile("(?i:[application|text]/json)") + xmlCheck = regexp.MustCompile("(?i:[application|text]/xml)") +) + +// APIClient manages communication with the MEC Sandbox API API v0.0.7 +// In most cases there should be only one, shared, APIClient. +type APIClient struct { + cfg *Configuration + common service // Reuse a single struct instead of allocating one for each service on the heap. + + // API Services + + AuthorizationApi *AuthorizationApiService + + SandboxAppInstancesApi *SandboxAppInstancesApiService + + SandboxLogsSubscriptionsApi *SandboxLogsSubscriptionsApiService + + SandboxMECServicesApi *SandboxMECServicesApiService + + SandboxNetworkScenariosApi *SandboxNetworkScenariosApiService + + SandboxUEControllerApi *SandboxUEControllerApiService +} + +type service struct { + client *APIClient +} + +// NewAPIClient creates a new API client. Requires a userAgent string describing your application. +// optionally a custom http.Client to allow for advanced features such as caching. +func NewAPIClient(cfg *Configuration) *APIClient { + if cfg.HTTPClient == nil { + cfg.HTTPClient = http.DefaultClient + } + + c := &APIClient{} + c.cfg = cfg + c.common.client = c + + // API Services + c.AuthorizationApi = (*AuthorizationApiService)(&c.common) + c.SandboxAppInstancesApi = (*SandboxAppInstancesApiService)(&c.common) + c.SandboxLogsSubscriptionsApi = (*SandboxLogsSubscriptionsApiService)(&c.common) + c.SandboxMECServicesApi = (*SandboxMECServicesApiService)(&c.common) + c.SandboxNetworkScenariosApi = (*SandboxNetworkScenariosApiService)(&c.common) + c.SandboxUEControllerApi = (*SandboxUEControllerApiService)(&c.common) + + return c +} + +func atoi(in string) (int, error) { + return strconv.Atoi(in) +} + +// selectHeaderContentType select a content type from the available list. +func selectHeaderContentType(contentTypes []string) string { + if len(contentTypes) == 0 { + return "" + } + if contains(contentTypes, "application/json") { + return "application/json" + } + return contentTypes[0] // use the first content type specified in 'consumes' +} + +// selectHeaderAccept join all accept types and return +func selectHeaderAccept(accepts []string) string { + if len(accepts) == 0 { + return "" + } + + if contains(accepts, "application/json") { + return "application/json" + } + + return strings.Join(accepts, ",") +} + +// contains is a case insenstive match, finding needle in a haystack +func contains(haystack []string, needle string) bool { + for _, a := range haystack { + if strings.ToLower(a) == strings.ToLower(needle) { + return true + } + } + return false +} + +// Verify optional parameters are of the correct type. +func typeCheckParameter(obj interface{}, expected string, name string) error { + // Make sure there is an object. + if obj == nil { + return nil + } + + // Check the type is as expected. + if reflect.TypeOf(obj).String() != expected { + return fmt.Errorf("Expected %s to be of type %s but received %s.", name, expected, reflect.TypeOf(obj).String()) + } + return nil +} + +// parameterToString convert interface{} parameters to string, using a delimiter if format is provided. +func parameterToString(obj interface{}, collectionFormat string) string { + var delimiter string + + switch collectionFormat { + case "pipes": + delimiter = "|" + case "ssv": + delimiter = " " + case "tsv": + delimiter = "\t" + case "csv": + delimiter = "," + } + + if reflect.TypeOf(obj).Kind() == reflect.Slice { + return strings.Trim(strings.Replace(fmt.Sprint(obj), " ", delimiter, -1), "[]") + } + + return fmt.Sprintf("%v", obj) +} + +// callAPI do the request. +func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) { + return c.cfg.HTTPClient.Do(request) +} + +// Change base path to allow switching to mocks +func (c *APIClient) ChangeBasePath(path string) { + c.cfg.BasePath = path +} + +// prepareRequest build the request +func (c *APIClient) prepareRequest( + ctx context.Context, + path string, method string, + postBody interface{}, + headerParams map[string]string, + queryParams url.Values, + formParams url.Values, + fileName string, + fileBytes []byte) (localVarRequest *http.Request, err error) { + + var body *bytes.Buffer + + // Detect postBody type and post. + if postBody != nil { + contentType := headerParams["Content-Type"] + if contentType == "" { + contentType = detectContentType(postBody) + headerParams["Content-Type"] = contentType + } + + body, err = setBody(postBody, contentType) + if err != nil { + return nil, err + } + } + + // add form parameters and file if available. + if strings.HasPrefix(headerParams["Content-Type"], "multipart/form-data") && len(formParams) > 0 || (len(fileBytes) > 0 && fileName != "") { + if body != nil { + return nil, errors.New("Cannot specify postBody and multipart form at the same time.") + } + body = &bytes.Buffer{} + w := multipart.NewWriter(body) + + for k, v := range formParams { + for _, iv := range v { + if strings.HasPrefix(k, "@") { // file + err = addFile(w, k[1:], iv) + if err != nil { + return nil, err + } + } else { // form value + w.WriteField(k, iv) + } + } + } + if len(fileBytes) > 0 && fileName != "" { + w.Boundary() + //_, fileNm := filepath.Split(fileName) + part, err := w.CreateFormFile("file", filepath.Base(fileName)) + if err != nil { + return nil, err + } + _, err = part.Write(fileBytes) + if err != nil { + return nil, err + } + // Set the Boundary in the Content-Type + headerParams["Content-Type"] = w.FormDataContentType() + } + + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + w.Close() + } + + if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 { + if body != nil { + return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.") + } + body = &bytes.Buffer{} + body.WriteString(formParams.Encode()) + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + } + + // Setup path and query parameters + url, err := url.Parse(path) + if err != nil { + return nil, err + } + + // Adding Query Param + query := url.Query() + for k, v := range queryParams { + for _, iv := range v { + query.Add(k, iv) + } + } + + // Encode the parameters. + url.RawQuery = query.Encode() + + // Generate a new request + if body != nil { + localVarRequest, err = http.NewRequest(method, url.String(), body) + } else { + localVarRequest, err = http.NewRequest(method, url.String(), nil) + } + if err != nil { + return nil, err + } + + // add header parameters, if any + if len(headerParams) > 0 { + headers := http.Header{} + for h, v := range headerParams { + headers.Set(h, v) + } + localVarRequest.Header = headers + } + + // Override request host, if applicable + if c.cfg.Host != "" { + localVarRequest.Host = c.cfg.Host + } + + // Add the user agent to the request. + localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent) + + if ctx != nil { + // add context to the request + localVarRequest = localVarRequest.WithContext(ctx) + + // Walk through any authentication. + + // OAuth2 authentication + if tok, ok := ctx.Value(ContextOAuth2).(oauth2.TokenSource); ok { + // We were able to grab an oauth2 token from the context + var latestToken *oauth2.Token + if latestToken, err = tok.Token(); err != nil { + return nil, err + } + + latestToken.SetAuthHeader(localVarRequest) + } + + // Basic HTTP Authentication + if auth, ok := ctx.Value(ContextBasicAuth).(BasicAuth); ok { + localVarRequest.SetBasicAuth(auth.UserName, auth.Password) + } + + // AccessToken Authentication + if auth, ok := ctx.Value(ContextAccessToken).(string); ok { + localVarRequest.Header.Add("Authorization", "Bearer "+auth) + } + } + + for header, value := range c.cfg.DefaultHeader { + localVarRequest.Header.Add(header, value) + } + + return localVarRequest, nil +} + +func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) { + if strings.Contains(contentType, "application/xml") { + if err = xml.Unmarshal(b, v); err != nil { + return err + } + return nil + } else if strings.Contains(contentType, "application/json") { + if err = json.Unmarshal(b, v); err != nil { + return err + } + return nil + } + return errors.New("undefined response type") +} + +// Add a file to the multipart request +func addFile(w *multipart.Writer, fieldName, path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + part, err := w.CreateFormFile(fieldName, filepath.Base(path)) + if err != nil { + return err + } + _, err = io.Copy(part, file) + + return err +} + +// Prevent trying to import "fmt" +func reportError(format string, a ...interface{}) error { + return fmt.Errorf(format, a...) +} + +// Set request body from an interface{} +func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) { + if bodyBuf == nil { + bodyBuf = &bytes.Buffer{} + } + + if reader, ok := body.(io.Reader); ok { + _, err = bodyBuf.ReadFrom(reader) + } else if b, ok := body.([]byte); ok { + _, err = bodyBuf.Write(b) + } else if s, ok := body.(string); ok { + _, err = bodyBuf.WriteString(s) + } else if s, ok := body.(*string); ok { + _, err = bodyBuf.WriteString(*s) + } else if jsonCheck.MatchString(contentType) { + err = json.NewEncoder(bodyBuf).Encode(body) + } else if xmlCheck.MatchString(contentType) { + xml.NewEncoder(bodyBuf).Encode(body) + } + + if err != nil { + return nil, err + } + + if bodyBuf.Len() == 0 { + err = fmt.Errorf("Invalid body type %s\n", contentType) + return nil, err + } + return bodyBuf, nil +} + +// detectContentType method is used to figure out `Request.Body` content type for request header +func detectContentType(body interface{}) string { + contentType := "text/plain; charset=utf-8" + kind := reflect.TypeOf(body).Kind() + + switch kind { + case reflect.Struct, reflect.Map, reflect.Ptr: + contentType = "application/json; charset=utf-8" + case reflect.String: + contentType = "text/plain; charset=utf-8" + default: + if b, ok := body.([]byte); ok { + contentType = http.DetectContentType(b) + } else if kind == reflect.Slice { + contentType = "application/json; charset=utf-8" + } + } + + return contentType +} + +// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go +type cacheControl map[string]string + +func parseCacheControl(headers http.Header) cacheControl { + cc := cacheControl{} + ccHeader := headers.Get("Cache-Control") + for _, part := range strings.Split(ccHeader, ",") { + part = strings.Trim(part, " ") + if part == "" { + continue + } + if strings.ContainsRune(part, '=') { + keyval := strings.Split(part, "=") + cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",") + } else { + cc[part] = "" + } + } + return cc +} + +// CacheExpires helper function to determine remaining time before repeating a request. +func CacheExpires(r *http.Response) time.Time { + // Figure out when the cache expires. + var expires time.Time + now, err := time.Parse(time.RFC1123, r.Header.Get("date")) + if err != nil { + return time.Now() + } + respCacheControl := parseCacheControl(r.Header) + + if maxAge, ok := respCacheControl["max-age"]; ok { + lifetime, err := time.ParseDuration(maxAge + "s") + if err != nil { + expires = now + } + expires = now.Add(lifetime) + } else { + expiresHeader := r.Header.Get("Expires") + if expiresHeader != "" { + expires, err = time.Parse(time.RFC1123, expiresHeader) + if err != nil { + expires = now + } + } + } + return expires +} + +func strlen(s string) int { + return utf8.RuneCountInString(s) +} + +// GenericSwaggerError Provides access to the body, error and model on returned errors. +type GenericSwaggerError struct { + body []byte + error string + model interface{} +} + +// Error returns non-empty string if there was an error. +func (e GenericSwaggerError) Error() string { + return e.error +} + +// Body returns the raw bytes of the response +func (e GenericSwaggerError) Body() []byte { + return e.body +} + +// Model returns the unpacked model of the error +func (e GenericSwaggerError) Model() interface{} { + return e.model +} diff --git a/examples/demo9/golang/client/configuration.go b/examples/demo9/golang/client/configuration.go new file mode 100644 index 0000000000000000000000000000000000000000..f3f36dccec1e905ee4ab2ae542449f686efaf0b6 --- /dev/null +++ b/examples/demo9/golang/client/configuration.go @@ -0,0 +1,72 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "net/http" +) + +// contextKeys are used to identify the type of value in the context. +// Since these are string, it is possible to get a short description of the +// context key for logging and debugging using key.String(). + +type contextKey string + +func (c contextKey) String() string { + return "auth " + string(c) +} + +var ( + // ContextOAuth2 takes a oauth2.TokenSource as authentication for the request. + ContextOAuth2 = contextKey("token") + + // ContextBasicAuth takes BasicAuth as authentication for the request. + ContextBasicAuth = contextKey("basic") + + // ContextAccessToken takes a string oauth2 access token as authentication for the request. + ContextAccessToken = contextKey("accesstoken") + + // ContextAPIKey takes an APIKey as authentication for the request + ContextAPIKey = contextKey("apikey") +) + +// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth +type BasicAuth struct { + UserName string `json:"userName,omitempty"` + Password string `json:"password,omitempty"` +} + +// APIKey provides API key based authentication to a request passed via context using ContextAPIKey +type APIKey struct { + Key string + Prefix string +} + +type Configuration struct { + BasePath string `json:"basePath,omitempty"` + Host string `json:"host,omitempty"` + Scheme string `json:"scheme,omitempty"` + DefaultHeader map[string]string `json:"defaultHeader,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + HTTPClient *http.Client +} + +func NewConfiguration() *Configuration { + cfg := &Configuration{ + BasePath: "http://localhost/sandbox-api/v1", + DefaultHeader: make(map[string]string), + UserAgent: "Swagger-Codegen/1.0.0/go", + } + return cfg +} + +func (c *Configuration) AddDefaultHeader(key string, value string) { + c.DefaultHeader[key] = value +} diff --git a/examples/demo9/golang/client/docs/ApplicationInfo.md b/examples/demo9/golang/client/docs/ApplicationInfo.md new file mode 100644 index 0000000000000000000000000000000000000000..1c7a5def7b208101a845b75f8fd28df5682fa866 --- /dev/null +++ b/examples/demo9/golang/client/docs/ApplicationInfo.md @@ -0,0 +1,13 @@ +# ApplicationInfo + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | Application Instance UUID | [optional] [default to null] +**Name** | **string** | Application name | [default to null] +**NodeName** | **string** | Name of node where application instance is running | [default to null] +**Type_** | **string** | Application Type | [optional] [default to null] +**Persist** | **bool** | Reserved for internal platform usage | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/AuthorizationApi.md b/examples/demo9/golang/client/docs/AuthorizationApi.md new file mode 100644 index 0000000000000000000000000000000000000000..7baf4f30a0bd875c364a0a828c65259504e32cec --- /dev/null +++ b/examples/demo9/golang/client/docs/AuthorizationApi.md @@ -0,0 +1,65 @@ +# {{classname}} + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**Login**](AuthorizationApi.md#Login) | **Post** /login | Initiate OAuth login procedure and creates a MEC Sandbox instance +[**Logout**](AuthorizationApi.md#Logout) | **Post** /logout | Terminates User Session and delete the Sandbox instance + +# **Login** +> Sandbox Login(ctx, provider) +Initiate OAuth login procedure and creates a MEC Sandbox instance + +Initiate OAuth login procedure and creates a MEC Sandbox instance + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **provider** | **string**| Oauth provider | + +### Return type + +[**Sandbox**](Sandbox.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **Logout** +> Logout(ctx, sandboxName) +Terminates User Session and delete the Sandbox instance + +Terminates User Session and delete the Sandbox instance + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/CellularDomainConfig.md b/examples/demo9/golang/client/docs/CellularDomainConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..8367435a4276e9b4cfea9a175ac537a090da6029 --- /dev/null +++ b/examples/demo9/golang/client/docs/CellularDomainConfig.md @@ -0,0 +1,11 @@ +# CellularDomainConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Mnc** | **string** | Mobile Network Code part of PLMN identity as defined in ETSI TS 136 413 | [optional] [default to null] +**Mcc** | **string** | Mobile Country Code part of PLMN identity as defined in ETSI TS 136 413 | [optional] [default to null] +**DefaultCellId** | **string** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 if no cellId is defined for the cell or if not applicable | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/CellularPoaConfig.md b/examples/demo9/golang/client/docs/CellularPoaConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..0f5fa55b4bc45eba61b771e8977aba249ceda2c8 --- /dev/null +++ b/examples/demo9/golang/client/docs/CellularPoaConfig.md @@ -0,0 +1,9 @@ +# CellularPoaConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**CellId** | **string** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/ConnectivityConfig.md b/examples/demo9/golang/client/docs/ConnectivityConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..4c40b58d7b8cb505ddff8aacba067a6c0e1db0fe --- /dev/null +++ b/examples/demo9/golang/client/docs/ConnectivityConfig.md @@ -0,0 +1,9 @@ +# ConnectivityConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Model** | **string** | Connectivity Model: <li>OPEN: Any node in the scenario can communicate with any node <li>PDU: Terminal nodes (UE) require a PDU session to the target DN | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/CpuConfig.md b/examples/demo9/golang/client/docs/CpuConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..83e32983f3548c0ae64d31916f2bb5fb5844107f --- /dev/null +++ b/examples/demo9/golang/client/docs/CpuConfig.md @@ -0,0 +1,10 @@ +# CpuConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Min** | **float32** | Minimum requested CPU | [optional] [default to null] +**Max** | **float32** | Maximum requested CPU | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/D2dConfig.md b/examples/demo9/golang/client/docs/D2dConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..3876c88484bf376f68a23ba64e2aac19897f0546 --- /dev/null +++ b/examples/demo9/golang/client/docs/D2dConfig.md @@ -0,0 +1,10 @@ +# D2dConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**D2dMaxDistance** | **float64** | Maximum distance for D2D. Default distance is 100m | [optional] [default to null] +**DisableD2dViaNetwork** | **bool** | Enable-Disable D2D via network. Default value is false | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Deployment.md b/examples/demo9/golang/client/docs/Deployment.md new file mode 100644 index 0000000000000000000000000000000000000000..cd15f49ca75bf8de44285618518453bce721257a --- /dev/null +++ b/examples/demo9/golang/client/docs/Deployment.md @@ -0,0 +1,18 @@ +# Deployment + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**NetChar** | [***NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] [default to null] +**Connectivity** | [***ConnectivityConfig**](ConnectivityConfig.md) | | [optional] [default to null] +**D2d** | [***D2dConfig**](D2dConfig.md) | | [optional] [default to null] +**InterDomainLatency** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] [default to null] +**InterDomainLatencyVariation** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] [default to null] +**InterDomainThroughput** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] [default to null] +**InterDomainPacketLoss** | **float64** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] [default to null] +**Meta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**UserMeta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**Domains** | [**[]Domain**](Domain.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/DnConfig.md b/examples/demo9/golang/client/docs/DnConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..3e114f2ff6779035368e78c5dc83e9f1da70fec4 --- /dev/null +++ b/examples/demo9/golang/client/docs/DnConfig.md @@ -0,0 +1,11 @@ +# DnConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Dnn** | **string** | Data Network Name | [optional] [default to null] +**Ladn** | **bool** | true: Data network serves local area only false: Data network is not limited to local area | [optional] [default to null] +**Ecsp** | **string** | Edge Compute Service Provider | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Domain.md b/examples/demo9/golang/client/docs/Domain.md new file mode 100644 index 0000000000000000000000000000000000000000..df60d20427ad775e968efa5f3e0d0ba8a9ed4a54 --- /dev/null +++ b/examples/demo9/golang/client/docs/Domain.md @@ -0,0 +1,20 @@ +# Domain + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | Unique domain ID | [optional] [default to null] +**Name** | **string** | Domain name | [optional] [default to null] +**Type_** | **string** | Domain type | [optional] [default to null] +**NetChar** | [***NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] [default to null] +**InterZoneLatency** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] [default to null] +**InterZoneLatencyVariation** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] [default to null] +**InterZoneThroughput** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] [default to null] +**InterZonePacketLoss** | **float64** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] [default to null] +**Meta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**UserMeta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**CellularDomainConfig** | [***CellularDomainConfig**](CellularDomainConfig.md) | | [optional] [default to null] +**Zones** | [**[]Zone**](Zone.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/EgressService.md b/examples/demo9/golang/client/docs/EgressService.md new file mode 100644 index 0000000000000000000000000000000000000000..ae3680f97d23be713c0dc90f597eb4591f80c7f9 --- /dev/null +++ b/examples/demo9/golang/client/docs/EgressService.md @@ -0,0 +1,13 @@ +# EgressService + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Name** | **string** | Service name | [optional] [default to null] +**MeSvcName** | **string** | Multi-Edge service name, if any | [optional] [default to null] +**Ip** | **string** | External node IP address | [optional] [default to null] +**Port** | **int32** | Service port number | [optional] [default to null] +**Protocol** | **string** | Service protocol (TCP or UDP) | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/ExternalConfig.md b/examples/demo9/golang/client/docs/ExternalConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..729b6bded106722df302b5c9825e6b52ba770107 --- /dev/null +++ b/examples/demo9/golang/client/docs/ExternalConfig.md @@ -0,0 +1,10 @@ +# ExternalConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**IngressServiceMap** | [**[]IngressService**](IngressService.md) | | [optional] [default to null] +**EgressServiceMap** | [**[]EgressService**](EgressService.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/GeoData.md b/examples/demo9/golang/client/docs/GeoData.md new file mode 100644 index 0000000000000000000000000000000000000000..34f79176879f1bda8affa5667518f8049920229d --- /dev/null +++ b/examples/demo9/golang/client/docs/GeoData.md @@ -0,0 +1,15 @@ +# GeoData + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Location** | [***Point**](Point.md) | | [optional] [default to null] +**Radius** | **float64** | Optional - Radius (in meters) around the location | [optional] [default to null] +**Path** | [***LineString**](LineString.md) | | [optional] [default to null] +**EopMode** | **string** | End-of-Path mode: <li>LOOP: When path endpoint is reached, start over from the beginning <li>REVERSE: When path endpoint is reached, return on the reverse path | [optional] [default to null] +**Velocity** | **float64** | Speed of movement along path in m/s | [optional] [default to null] +**D2dInRange** | **[]string** | | [optional] [default to null] +**PoaInRange** | **[]string** | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/GpuConfig.md b/examples/demo9/golang/client/docs/GpuConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..74ea3c7fbbb67b6168450cdf75d3e09e0cddebc9 --- /dev/null +++ b/examples/demo9/golang/client/docs/GpuConfig.md @@ -0,0 +1,10 @@ +# GpuConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Type_** | **string** | Requested GPU type | [optional] [default to null] +**Count** | **int32** | Number of GPUs requested | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/IngressService.md b/examples/demo9/golang/client/docs/IngressService.md new file mode 100644 index 0000000000000000000000000000000000000000..7a36f729186ec4866719d2517a771ef7886def95 --- /dev/null +++ b/examples/demo9/golang/client/docs/IngressService.md @@ -0,0 +1,12 @@ +# IngressService + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Name** | **string** | Service name (unique or multi-edge) | [optional] [default to null] +**Port** | **int32** | Internal service port number | [optional] [default to null] +**ExternalPort** | **int32** | Externally-exposed unique service port in range (30000 - 32767) | [optional] [default to null] +**Protocol** | **string** | Service protocol (TCP or UDP) | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/LineString.md b/examples/demo9/golang/client/docs/LineString.md new file mode 100644 index 0000000000000000000000000000000000000000..34db0599e7fbd8ef9f526bd43ae41e58e91109ca --- /dev/null +++ b/examples/demo9/golang/client/docs/LineString.md @@ -0,0 +1,10 @@ +# LineString + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Type_** | **string** | Must be LineString | [default to null] +**Coordinates** | [**[][]float64**](array.md) | For a LineString, coordinates is an array of two or more positions; a position is an array of two decimal numbers (longitude and latitude precisely in that order) | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/MemoryConfig.md b/examples/demo9/golang/client/docs/MemoryConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..a7236943325d3e7983361896d2508fe3c09f39a6 --- /dev/null +++ b/examples/demo9/golang/client/docs/MemoryConfig.md @@ -0,0 +1,10 @@ +# MemoryConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Min** | **int32** | Minimum requested memory | [optional] [default to null] +**Max** | **int32** | Maximum requested memory | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/NetworkCharacteristics.md b/examples/demo9/golang/client/docs/NetworkCharacteristics.md new file mode 100644 index 0000000000000000000000000000000000000000..277d6b1fd26a005cc49e3a17e1f883585916551d --- /dev/null +++ b/examples/demo9/golang/client/docs/NetworkCharacteristics.md @@ -0,0 +1,15 @@ +# NetworkCharacteristics + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Latency** | **int32** | Latency in ms | [optional] [default to null] +**LatencyVariation** | **int32** | Latency variation in ms | [optional] [default to null] +**LatencyDistribution** | **string** | Latency distribution. Can only be set in the Scenario Deployment network characteristics, ignored otherwise. Latency distribution is set for the whole network and applied to every end-to-end traffic flows. Default value is 'Normal' distribution. | [optional] [default to null] +**Throughput** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by throughputUl and throughputDl | [optional] [default to null] +**ThroughputDl** | **int32** | Downlink throughput limit in Mbps | [optional] [default to null] +**ThroughputUl** | **int32** | Uplink throughput limit in Mbps | [optional] [default to null] +**PacketLoss** | **float64** | Packet loss percentage | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/NetworkLocation.md b/examples/demo9/golang/client/docs/NetworkLocation.md new file mode 100644 index 0000000000000000000000000000000000000000..1fad2ab714087f540458c600a63a177ec3a2951b --- /dev/null +++ b/examples/demo9/golang/client/docs/NetworkLocation.md @@ -0,0 +1,24 @@ +# NetworkLocation + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | Unique network location ID | [optional] [default to null] +**Name** | **string** | Network location name | [optional] [default to null] +**Type_** | **string** | Network location type | [optional] [default to null] +**NetChar** | [***NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] [default to null] +**TerminalLinkLatency** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] [default to null] +**TerminalLinkLatencyVariation** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] [default to null] +**TerminalLinkThroughput** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] [default to null] +**TerminalLinkPacketLoss** | **float64** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] [default to null] +**Meta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**UserMeta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**CellularPoaConfig** | [***CellularPoaConfig**](CellularPoaConfig.md) | | [optional] [default to null] +**Poa4GConfig** | [***Poa4GConfig**](Poa4GConfig.md) | | [optional] [default to null] +**Poa5GConfig** | [***Poa5GConfig**](Poa5GConfig.md) | | [optional] [default to null] +**PoaWifiConfig** | [***PoaWifiConfig**](PoaWifiConfig.md) | | [optional] [default to null] +**GeoData** | [***GeoData**](GeoData.md) | | [optional] [default to null] +**PhysicalLocations** | [**[]PhysicalLocation**](PhysicalLocation.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/PhysicalLocation.md b/examples/demo9/golang/client/docs/PhysicalLocation.md new file mode 100644 index 0000000000000000000000000000000000000000..01cf0a8a006d41b51229498a07c72ec7ce8dec59 --- /dev/null +++ b/examples/demo9/golang/client/docs/PhysicalLocation.md @@ -0,0 +1,27 @@ +# PhysicalLocation + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | Unique physical location ID | [optional] [default to null] +**Name** | **string** | Physical location name | [optional] [default to null] +**Type_** | **string** | Physical location type | [optional] [default to null] +**IsExternal** | **bool** | true: Physical location is external to MEEP false: Physical location is internal to MEEP | [optional] [default to null] +**GeoData** | [***GeoData**](GeoData.md) | | [optional] [default to null] +**NetworkLocationsInRange** | **[]string** | | [optional] [default to null] +**Connected** | **bool** | true: Physical location has network connectivity false: Physical location has no network connectivity | [optional] [default to null] +**Wireless** | **bool** | true: Physical location uses a wireless connection false: Physical location uses a wired connection | [optional] [default to null] +**WirelessType** | **string** | Prioritized, comma-separated list of supported wireless connection types. Default priority if not specififed is 'wifi,5g,4g,other'. Wireless connection types: - 4g - 5g - wifi - other | [optional] [default to null] +**DataNetwork** | [***DnConfig**](DNConfig.md) | | [optional] [default to null] +**Meta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**UserMeta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**Processes** | [**[]Process**](Process.md) | | [optional] [default to null] +**NetChar** | [***NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] [default to null] +**LinkLatency** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] [default to null] +**LinkLatencyVariation** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] [default to null] +**LinkThroughput** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] [default to null] +**LinkPacketLoss** | **float64** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] [default to null] +**MacId** | **string** | Physical location MAC Address | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Poa4GConfig.md b/examples/demo9/golang/client/docs/Poa4GConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..b3c288bb4ba808ba9d3cebdbd92c1b37a4f75ede --- /dev/null +++ b/examples/demo9/golang/client/docs/Poa4GConfig.md @@ -0,0 +1,9 @@ +# Poa4GConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**CellId** | **string** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Poa5GConfig.md b/examples/demo9/golang/client/docs/Poa5GConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..cb74001c55e90e492f34fd71748d6900de7c64c8 --- /dev/null +++ b/examples/demo9/golang/client/docs/Poa5GConfig.md @@ -0,0 +1,9 @@ +# Poa5GConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**CellId** | **string** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the NR serving the cell | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/PoaWifiConfig.md b/examples/demo9/golang/client/docs/PoaWifiConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..91c4a1c3cbc89945def9214d6701094d8f5f9fcf --- /dev/null +++ b/examples/demo9/golang/client/docs/PoaWifiConfig.md @@ -0,0 +1,9 @@ +# PoaWifiConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**MacId** | **string** | WIFI POA MAC Address | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Point.md b/examples/demo9/golang/client/docs/Point.md new file mode 100644 index 0000000000000000000000000000000000000000..ccf42e60ca10fdd5144893acb51324fa154d99c2 --- /dev/null +++ b/examples/demo9/golang/client/docs/Point.md @@ -0,0 +1,10 @@ +# Point + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Type_** | **string** | Must be Point | [default to null] +**Coordinates** | **[]float64** | For a Point, coordinates MUST be an array of two decimal numbers; longitude and latitude precisely in that order | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/ProblemDetails.md b/examples/demo9/golang/client/docs/ProblemDetails.md new file mode 100644 index 0000000000000000000000000000000000000000..b679201f203c0b7f989981477b51fcdbbc9eb4e3 --- /dev/null +++ b/examples/demo9/golang/client/docs/ProblemDetails.md @@ -0,0 +1,13 @@ +# ProblemDetails + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Type_** | **string** | A URI reference according to IETF RFC 3986 that identifies the problem type. It is encouraged that the URI provides human-readable documentation for the problem (e.g. using HTML) when dereferenced. When this member is not present, its value is assumed to be \"about:blank\". | [optional] [default to null] +**Title** | **string** | A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization. If type is given and other than \"about:blank\", this attribute shall also be provided. A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4). | [optional] [default to null] +**Status** | **int32** | The HTTP status code for this occurrence of the problem. The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. | [default to null] +**Detail** | **string** | A human-readable explanation specific to this occurrence of the problem. | [default to null] +**Instance** | **string** | A URI reference that identifies the specific occurrence of the problem. It may yield further information if dereferenced. | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Process.md b/examples/demo9/golang/client/docs/Process.md new file mode 100644 index 0000000000000000000000000000000000000000..99a69069618e059e4db07b06d9c31f588b69d52c --- /dev/null +++ b/examples/demo9/golang/client/docs/Process.md @@ -0,0 +1,33 @@ +# Process + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | Unique process ID | [optional] [default to null] +**Name** | **string** | Process name | [optional] [default to null] +**Type_** | **string** | Process type | [optional] [default to null] +**IsExternal** | **bool** | true: process is external to MEEP false: process is internal to MEEP | [optional] [default to null] +**Image** | **string** | Docker image to deploy inside MEEP | [optional] [default to null] +**Environment** | **string** | Environment variables using the format NAME=\"value\",NAME=\"value\",NAME=\"value\" | [optional] [default to null] +**CommandArguments** | **string** | Arguments to command executable | [optional] [default to null] +**CommandExe** | **string** | Executable to invoke at container start up | [optional] [default to null] +**ServiceConfig** | [***ServiceConfig**](ServiceConfig.md) | | [optional] [default to null] +**GpuConfig** | [***GpuConfig**](GpuConfig.md) | | [optional] [default to null] +**MemoryConfig** | [***MemoryConfig**](MemoryConfig.md) | | [optional] [default to null] +**CpuConfig** | [***CpuConfig**](CpuConfig.md) | | [optional] [default to null] +**ExternalConfig** | [***ExternalConfig**](ExternalConfig.md) | | [optional] [default to null] +**Status** | **string** | Process status | [optional] [default to null] +**UserChartLocation** | **string** | Chart location for the deployment of the chart provided by the user | [optional] [default to null] +**UserChartAlternateValues** | **string** | Chart values.yaml file location for the deployment of the chart provided by the user | [optional] [default to null] +**UserChartGroup** | **string** | Chart supplemental information related to the group (service) | [optional] [default to null] +**Meta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**UserMeta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**NetChar** | [***NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] [default to null] +**AppLatency** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] [default to null] +**AppLatencyVariation** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] [default to null] +**AppThroughput** | **int32** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] [default to null] +**AppPacketLoss** | **float64** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] [default to null] +**PlacementId** | **string** | Identifier used for process placement in AdvantEDGE cluster | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Sandbox.md b/examples/demo9/golang/client/docs/Sandbox.md new file mode 100644 index 0000000000000000000000000000000000000000..4d7cee4c33982bf6c68b31933ed80e8bd2b55942 --- /dev/null +++ b/examples/demo9/golang/client/docs/Sandbox.md @@ -0,0 +1,9 @@ +# Sandbox + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Name** | **string** | Sandbox name | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxAppInstances.md b/examples/demo9/golang/client/docs/SandboxAppInstances.md new file mode 100644 index 0000000000000000000000000000000000000000..01b49cd200b9e41c836fa72d68c8eaa2983d6845 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxAppInstances.md @@ -0,0 +1,9 @@ +# SandboxAppInstances + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | The application instance identifier. | [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxAppInstancesApi.md b/examples/demo9/golang/client/docs/SandboxAppInstancesApi.md new file mode 100644 index 0000000000000000000000000000000000000000..03415aa39297a6a0abe5d193113bffea38b32f93 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxAppInstancesApi.md @@ -0,0 +1,96 @@ +# {{classname}} + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**SandboxAppInstancesDELETE**](SandboxAppInstancesApi.md#SandboxAppInstancesDELETE) | **Delete** /sandboxAppInstances/{sandbox_name}/{app_instance_id} | Delete an existing application instance +[**SandboxAppInstancesGET**](SandboxAppInstancesApi.md#SandboxAppInstancesGET) | **Get** /sandboxAppInstances/{sandbox_name} | Get the list of the available application instance identifiers +[**SandboxAppInstancesPOST**](SandboxAppInstancesApi.md#SandboxAppInstancesPOST) | **Post** /sandboxAppInstances/{sandbox_name} | Create a new application instance identifier + +# **SandboxAppInstancesDELETE** +> SandboxAppInstancesDELETE(ctx, sandboxName, appInstanceId) +Delete an existing application instance + +This method removes an existing application instance + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + **appInstanceId** | **string**| It uniquely identifies a MEC application instance identifier | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxAppInstancesGET** +> []SandboxAppInstances SandboxAppInstancesGET(ctx, sandboxName) +Get the list of the available application instance identifiers + +This method retrieves the list of the available application instance identifiers + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + +### Return type + +[**[]SandboxAppInstances**](SandboxAppInstances.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxAppInstancesPOST** +> []ApplicationInfo SandboxAppInstancesPOST(ctx, body, sandboxName) +Create a new application instance identifier + +This method creates a new application instance + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **body** | [**ApplicationInfo**](ApplicationInfo.md)| Pet to add to the store | + **sandboxName** | **string**| Sandbox identifier | + +### Return type + +[**[]ApplicationInfo**](ApplicationInfo.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxLogsSubscriptions.md b/examples/demo9/golang/client/docs/SandboxLogsSubscriptions.md new file mode 100644 index 0000000000000000000000000000000000000000..47166a649292a0696493a9bde551172be2d1f6b2 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxLogsSubscriptions.md @@ -0,0 +1,10 @@ +# SandboxLogsSubscriptions + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**CallbackReference** | **string** | The callback to notify log messages. | [default to null] +**SubscriptionReference** | **string** | The reference of the subscription. | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxLogsSubscriptionsApi.md b/examples/demo9/golang/client/docs/SandboxLogsSubscriptionsApi.md new file mode 100644 index 0000000000000000000000000000000000000000..173d9e171131e6b5797e10164a134e38a2f9f240 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxLogsSubscriptionsApi.md @@ -0,0 +1,66 @@ +# {{classname}} + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**SandboxLogsSubscriptionsDELETE**](SandboxLogsSubscriptionsApi.md#SandboxLogsSubscriptionsDELETE) | **Delete** /sandboxLogsSubscriptions/{sandbox_name}/{subscription_reference} | Subscription to receive logs from the sandbox +[**SandboxLogsSubscriptionsPOST**](SandboxLogsSubscriptionsApi.md#SandboxLogsSubscriptionsPOST) | **Post** /sandboxLogsSubscriptions/{sandbox_name} | Subscription to receive logs from the sandbox + +# **SandboxLogsSubscriptionsDELETE** +> SandboxLogsSubscriptionsDELETE(ctx, sandboxName, subscriptionReference) +Subscription to receive logs from the sandbox + +This method is used to receive logs from the sandbox. + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + **subscriptionReference** | **string**| It uniquely identifies subscription reference to receive logs from the sandbox | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxLogsSubscriptionsPOST** +> []SandboxLogsSubscriptions SandboxLogsSubscriptionsPOST(ctx, sandboxName) +Subscription to receive logs from the sandbox + +This method is used to receive logs from the sandbox. + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + +### Return type + +[**[]SandboxLogsSubscriptions**](SandboxLogsSubscriptions.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxMECServicesApi.md b/examples/demo9/golang/client/docs/SandboxMECServicesApi.md new file mode 100644 index 0000000000000000000000000000000000000000..f006ee5a587fe3420d6ff66fd8a9c2871c281feb --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxMECServicesApi.md @@ -0,0 +1,36 @@ +# {{classname}} + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**SandboxMecServicesGET**](SandboxMECServicesApi.md#SandboxMecServicesGET) | **Get** /sandboxMecServices/{sandbox_name} | Get the list of the available MEC services + +# **SandboxMecServicesGET** +> []SandboxMecServices SandboxMecServicesGET(ctx, sandboxName) +Get the list of the available MEC services + +This method retrieves the list of the available MEC services. + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + +### Return type + +[**[]SandboxMecServices**](SandboxMecServices.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxMecServices.md b/examples/demo9/golang/client/docs/SandboxMecServices.md new file mode 100644 index 0000000000000000000000000000000000000000..4d1ddec04229642b2420c4b49e162359e759715c --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxMecServices.md @@ -0,0 +1,10 @@ +# SandboxMecServices + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | The MEC service name. | [default to null] +**ServiceId** | **string** | When a MEC service is selected, this field contains a token which shall be used in MEC service API URI. | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxNetworkScenario.md b/examples/demo9/golang/client/docs/SandboxNetworkScenario.md new file mode 100644 index 0000000000000000000000000000000000000000..3f8a4d874186d2d18d8d096c54b14db5379174b5 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxNetworkScenario.md @@ -0,0 +1,9 @@ +# SandboxNetworkScenario + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | The network scenario name. | [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxNetworkScenariosApi.md b/examples/demo9/golang/client/docs/SandboxNetworkScenariosApi.md new file mode 100644 index 0000000000000000000000000000000000000000..53b8e66acb7cc33207fea9033498e42b20ac5088 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxNetworkScenariosApi.md @@ -0,0 +1,126 @@ +# {{classname}} + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**SandboxIndividualNetworkScenariosGET**](SandboxNetworkScenariosApi.md#SandboxIndividualNetworkScenariosGET) | **Get** /sandboxNetworkScenarios/{sandbox_name} | Get description of a Network Scenario to be used. +[**SandboxNetworkScenarioDELETE**](SandboxNetworkScenariosApi.md#SandboxNetworkScenarioDELETE) | **Delete** /sandboxNetworkScenarios/{sandbox_name}/{network_scenario_id} | Deactivate the Network Scenario. +[**SandboxNetworkScenarioPOST**](SandboxNetworkScenariosApi.md#SandboxNetworkScenarioPOST) | **Post** /sandboxNetworkScenarios/{sandbox_name} | Selects the Network Scenario to be activated. +[**SandboxNetworkScenariosGET**](SandboxNetworkScenariosApi.md#SandboxNetworkScenariosGET) | **Get** /sandboxNetworkScenarios | Get the list of the available network scenarios + +# **SandboxIndividualNetworkScenariosGET** +> []Scenario SandboxIndividualNetworkScenariosGET(ctx, sandboxName, networkScenarioId) +Get description of a Network Scenario to be used. + +This method retrive description of a the network scenario + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + **networkScenarioId** | **string**| Network scenario to retrieve | + +### Return type + +[**[]Scenario**](Scenario.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxNetworkScenarioDELETE** +> SandboxNetworkScenarioDELETE(ctx, sandboxName, networkScenarioId) +Deactivate the Network Scenario. + +This method deactivates the network scenario + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + **networkScenarioId** | **string**| Network scenario to be used | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxNetworkScenarioPOST** +> SandboxNetworkScenarioPOST(ctx, sandboxName, networkScenarioId) +Selects the Network Scenario to be activated. + +This method selects the network scenario to be activated. This request initiates the creation of necessary MEC services for specific network scenario + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + **networkScenarioId** | **string**| Network scenario to be used | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxNetworkScenariosGET** +> []SandboxNetworkScenario SandboxNetworkScenariosGET(ctx, sandboxName) +Get the list of the available network scenarios + +This method retrieves the list of the available network scenarios. + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + +### Return type + +[**[]SandboxNetworkScenario**](SandboxNetworkScenario.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/SandboxUEControllerApi.md b/examples/demo9/golang/client/docs/SandboxUEControllerApi.md new file mode 100644 index 0000000000000000000000000000000000000000..6f20afcef8704452bf69548fc38a0c1545e0d5f6 --- /dev/null +++ b/examples/demo9/golang/client/docs/SandboxUEControllerApi.md @@ -0,0 +1,67 @@ +# {{classname}} + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**SandboxUeControllerGET**](SandboxUEControllerApi.md#SandboxUeControllerGET) | **Get** /sandboxUeController/{sandbox_name} | Get the list of the available UEs (e.g. \"Stationary UE\") +[**SandboxUeControllerPATCH**](SandboxUEControllerApi.md#SandboxUeControllerPATCH) | **Patch** /sandboxUeController/{sandbox_name} | set the new value of the UE + +# **SandboxUeControllerGET** +> []Ue SandboxUeControllerGET(ctx, sandboxName) +Get the list of the available UEs (e.g. \"Stationary UE\") + +This method retrieves the list of the available available UEs. + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + +### Return type + +[**[]Ue**](UE.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **SandboxUeControllerPATCH** +> SandboxUeControllerPATCH(ctx, sandboxName, userEquipmentId, userEquipmentValue) +set the new value of the UE + +This method sets the new value of the UE. + +### Required Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. + **sandboxName** | **string**| Sandbox identifier | + **userEquipmentId** | **string**| User equipmenet identifier | + **userEquipmentValue** | **int32**| It uniquely identifies a UE to set the new value | + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Scenario.md b/examples/demo9/golang/client/docs/Scenario.md new file mode 100644 index 0000000000000000000000000000000000000000..5c344aa590e65f41ebe2d32297ee2ee3a416bd04 --- /dev/null +++ b/examples/demo9/golang/client/docs/Scenario.md @@ -0,0 +1,14 @@ +# Scenario + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Version** | **string** | Scenario version | [optional] [default to null] +**Id** | **string** | Unique scenario ID | [optional] [default to null] +**Name** | **string** | Unique scenario name | [optional] [default to null] +**Description** | **string** | User description of the scenario. | [optional] [default to null] +**Config** | [***ScenarioConfig**](ScenarioConfig.md) | | [optional] [default to null] +**Deployment** | [***Deployment**](Deployment.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/ScenarioConfig.md b/examples/demo9/golang/client/docs/ScenarioConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..38839d4faac530ee87f81573e426d9afccc90bc9 --- /dev/null +++ b/examples/demo9/golang/client/docs/ScenarioConfig.md @@ -0,0 +1,10 @@ +# ScenarioConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Visualization** | **string** | Visualization configuration | [optional] [default to null] +**Other** | **string** | Other scenario configuration | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/ServiceConfig.md b/examples/demo9/golang/client/docs/ServiceConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..9cebfe1255170105428750aca7f75eb1655613c2 --- /dev/null +++ b/examples/demo9/golang/client/docs/ServiceConfig.md @@ -0,0 +1,11 @@ +# ServiceConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Name** | **string** | Unique service name | [optional] [default to null] +**MeSvcName** | **string** | Multi-Edge service name, if any | [optional] [default to null] +**Ports** | [**[]ServicePort**](ServicePort.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/ServicePort.md b/examples/demo9/golang/client/docs/ServicePort.md new file mode 100644 index 0000000000000000000000000000000000000000..3202110354be225c0fbbe5fafa569ab6e852dc3e --- /dev/null +++ b/examples/demo9/golang/client/docs/ServicePort.md @@ -0,0 +1,11 @@ +# ServicePort + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Protocol** | **string** | Protocol that the application is using (TCP or UDP) | [optional] [default to null] +**Port** | **int32** | Port number that the service is listening on | [optional] [default to null] +**ExternalPort** | **int32** | External port number on which to expose the application (30000 - 32767) <li>Only one application allowed per external port <li>Scenario builder must configure to prevent conflicts | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Ue.md b/examples/demo9/golang/client/docs/Ue.md new file mode 100644 index 0000000000000000000000000000000000000000..ed3246b8e420ae579c81adbad02f8f4c9d71edc5 --- /dev/null +++ b/examples/demo9/golang/client/docs/Ue.md @@ -0,0 +1,9 @@ +# Ue + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | The UE name. | [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/docs/Zone.md b/examples/demo9/golang/client/docs/Zone.md new file mode 100644 index 0000000000000000000000000000000000000000..8a0635a1b6ddea88995b7a907bca8fc51a6dccd6 --- /dev/null +++ b/examples/demo9/golang/client/docs/Zone.md @@ -0,0 +1,27 @@ +# Zone + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Id** | **string** | Unique zone ID | [optional] [default to null] +**Name** | **string** | Zone name | [optional] [default to null] +**Type_** | **string** | Zone type | [optional] [default to null] +**NetChar** | [***NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] [default to null] +**InterFogLatency** | **int32** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterFogLatencyVariation** | **int32** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterFogThroughput** | **int32** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterFogPacketLoss** | **float64** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterEdgeLatency** | **int32** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterEdgeLatencyVariation** | **int32** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterEdgeThroughput** | **int32** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**InterEdgePacketLoss** | **float64** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] [default to null] +**EdgeFogLatency** | **int32** | **DEPRECATED** As of release 1.3.0, replaced by netChar latency | [optional] [default to null] +**EdgeFogLatencyVariation** | **int32** | **DEPRECATED** As of release 1.3.0, replaced by netChar latencyVariation | [optional] [default to null] +**EdgeFogThroughput** | **int32** | **DEPRECATED** As of release 1.3.0, replaced by netChar throughput | [optional] [default to null] +**EdgeFogPacketLoss** | **float64** | **DEPRECATED** As of release 1.3.0, replaced by netChar packetLoss | [optional] [default to null] +**Meta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**UserMeta** | **map[string]string** | Key/Value Pair Map (string, string) | [optional] [default to null] +**NetworkLocations** | [**[]NetworkLocation**](NetworkLocation.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/golang/client/git_push.sh b/examples/demo9/golang/client/git_push.sh new file mode 100644 index 0000000000000000000000000000000000000000..ae01b182ae9eb047d0999a496b060e62d7b01e5c --- /dev/null +++ b/examples/demo9/golang/client/git_push.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/examples/demo9/golang/client/model_application_info.go b/examples/demo9/golang/client/model_application_info.go new file mode 100644 index 0000000000000000000000000000000000000000..50dd3bd9a441f84034e7fce1293d631412782adf --- /dev/null +++ b/examples/demo9/golang/client/model_application_info.go @@ -0,0 +1,24 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// MEC Application instance information +type ApplicationInfo struct { + // Application Instance UUID + Id string `json:"id,omitempty"` + // Application name + Name string `json:"name"` + // Name of node where application instance is running + NodeName string `json:"nodeName"` + // Application Type + Type_ string `json:"type,omitempty"` + // Reserved for internal platform usage + Persist bool `json:"persist,omitempty"` +} diff --git a/examples/demo9/golang/client/model_cellular_domain_config.go b/examples/demo9/golang/client/model_cellular_domain_config.go new file mode 100644 index 0000000000000000000000000000000000000000..63db45372c94dac45cb6129aabe8504b5f8521cb --- /dev/null +++ b/examples/demo9/golang/client/model_cellular_domain_config.go @@ -0,0 +1,20 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Cellular domain configuration information +type CellularDomainConfig struct { + // Mobile Network Code part of PLMN identity as defined in ETSI TS 136 413 + Mnc string `json:"mnc,omitempty"` + // Mobile Country Code part of PLMN identity as defined in ETSI TS 136 413 + Mcc string `json:"mcc,omitempty"` + // The E-UTRAN Cell Identity as defined in ETSI TS 136 413 if no cellId is defined for the cell or if not applicable + DefaultCellId string `json:"defaultCellId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_cellular_poa_config.go b/examples/demo9/golang/client/model_cellular_poa_config.go new file mode 100644 index 0000000000000000000000000000000000000000..d0787b42d241a5b8f4cecc10641a9c3081ac0487 --- /dev/null +++ b/examples/demo9/golang/client/model_cellular_poa_config.go @@ -0,0 +1,16 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// **DEPRECATED** As of release 1.5.1, renamed to poa4GConfig +type CellularPoaConfig struct { + // The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell + CellId string `json:"cellId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_connectivity_config.go b/examples/demo9/golang/client/model_connectivity_config.go new file mode 100644 index 0000000000000000000000000000000000000000..aa092c010287f77c4743cd303eac8acbae2d7680 --- /dev/null +++ b/examples/demo9/golang/client/model_connectivity_config.go @@ -0,0 +1,15 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +type ConnectivityConfig struct { + // Connectivity Model:
  • OPEN: Any node in the scenario can communicate with any node
  • PDU: Terminal nodes (UE) require a PDU session to the target DN + Model string `json:"model,omitempty"` +} diff --git a/examples/demo9/golang/client/model_cpu_config.go b/examples/demo9/golang/client/model_cpu_config.go new file mode 100644 index 0000000000000000000000000000000000000000..7537511c1b6400490b29ce496c3366c389442953 --- /dev/null +++ b/examples/demo9/golang/client/model_cpu_config.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// CPU configuration object +type CpuConfig struct { + // Minimum requested CPU + Min float32 `json:"min,omitempty"` + // Maximum requested CPU + Max float32 `json:"max,omitempty"` +} diff --git a/examples/demo9/golang/client/model_d2d_config.go b/examples/demo9/golang/client/model_d2d_config.go new file mode 100644 index 0000000000000000000000000000000000000000..78a992bcaaf551a5d16b56384bcc06928fa94fbc --- /dev/null +++ b/examples/demo9/golang/client/model_d2d_config.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// D2D config +type D2dConfig struct { + // Maximum distance for D2D. Default distance is 100m + D2dMaxDistance float64 `json:"d2dMaxDistance,omitempty"` + // Enable-Disable D2D via network. Default value is false + DisableD2dViaNetwork bool `json:"disableD2dViaNetwork,omitempty"` +} diff --git a/examples/demo9/golang/client/model_deployment.go b/examples/demo9/golang/client/model_deployment.go new file mode 100644 index 0000000000000000000000000000000000000000..575ba0fe58caa417875df42d9d4fc5c869ad6937 --- /dev/null +++ b/examples/demo9/golang/client/model_deployment.go @@ -0,0 +1,30 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Network deployment object +type Deployment struct { + NetChar *NetworkCharacteristics `json:"netChar,omitempty"` + Connectivity *ConnectivityConfig `json:"connectivity,omitempty"` + D2d *D2dConfig `json:"d2d,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latency + InterDomainLatency int32 `json:"interDomainLatency,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation + InterDomainLatencyVariation int32 `json:"interDomainLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl + InterDomainThroughput int32 `json:"interDomainThroughput,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss + InterDomainPacketLoss float64 `json:"interDomainPacketLoss,omitempty"` + // Key/Value Pair Map (string, string) + Meta map[string]string `json:"meta,omitempty"` + // Key/Value Pair Map (string, string) + UserMeta map[string]string `json:"userMeta,omitempty"` + Domains []Domain `json:"domains,omitempty"` +} diff --git a/examples/demo9/golang/client/model_dn_config.go b/examples/demo9/golang/client/model_dn_config.go new file mode 100644 index 0000000000000000000000000000000000000000..efd086d34ef67eb72fc1e3e9a92011ccfdc80e68 --- /dev/null +++ b/examples/demo9/golang/client/model_dn_config.go @@ -0,0 +1,20 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Data Network Configuration +type DnConfig struct { + // Data Network Name + Dnn string `json:"dnn,omitempty"` + // true: Data network serves local area only false: Data network is not limited to local area + Ladn bool `json:"ladn,omitempty"` + // Edge Compute Service Provider + Ecsp string `json:"ecsp,omitempty"` +} diff --git a/examples/demo9/golang/client/model_domain.go b/examples/demo9/golang/client/model_domain.go new file mode 100644 index 0000000000000000000000000000000000000000..17370f049ce86e048ca4ae0cdefd26ebc8d5dbe5 --- /dev/null +++ b/examples/demo9/golang/client/model_domain.go @@ -0,0 +1,35 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Operator domain object +type Domain struct { + // Unique domain ID + Id string `json:"id,omitempty"` + // Domain name + Name string `json:"name,omitempty"` + // Domain type + Type_ string `json:"type,omitempty"` + NetChar *NetworkCharacteristics `json:"netChar,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latency + InterZoneLatency int32 `json:"interZoneLatency,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation + InterZoneLatencyVariation int32 `json:"interZoneLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl + InterZoneThroughput int32 `json:"interZoneThroughput,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss + InterZonePacketLoss float64 `json:"interZonePacketLoss,omitempty"` + // Key/Value Pair Map (string, string) + Meta map[string]string `json:"meta,omitempty"` + // Key/Value Pair Map (string, string) + UserMeta map[string]string `json:"userMeta,omitempty"` + CellularDomainConfig *CellularDomainConfig `json:"cellularDomainConfig,omitempty"` + Zones []Zone `json:"zones,omitempty"` +} diff --git a/examples/demo9/golang/client/model_egress_service.go b/examples/demo9/golang/client/model_egress_service.go new file mode 100644 index 0000000000000000000000000000000000000000..0d7c986000262a806cb65b364c44e8e13de49ece --- /dev/null +++ b/examples/demo9/golang/client/model_egress_service.go @@ -0,0 +1,24 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// External service exposed internally via specific port +type EgressService struct { + // Service name + Name string `json:"name,omitempty"` + // Multi-Edge service name, if any + MeSvcName string `json:"meSvcName,omitempty"` + // External node IP address + Ip string `json:"ip,omitempty"` + // Service port number + Port int32 `json:"port,omitempty"` + // Service protocol (TCP or UDP) + Protocol string `json:"protocol,omitempty"` +} diff --git a/examples/demo9/golang/client/model_external_config.go b/examples/demo9/golang/client/model_external_config.go new file mode 100644 index 0000000000000000000000000000000000000000..ff15ccfb743eee8052da434f5cecd2fa22b751b9 --- /dev/null +++ b/examples/demo9/golang/client/model_external_config.go @@ -0,0 +1,16 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// External Process configuration. NOTE: Only valid if 'isExternal' is set. +type ExternalConfig struct { + IngressServiceMap []IngressService `json:"ingressServiceMap,omitempty"` + EgressServiceMap []EgressService `json:"egressServiceMap,omitempty"` +} diff --git a/examples/demo9/golang/client/model_geo_data.go b/examples/demo9/golang/client/model_geo_data.go new file mode 100644 index 0000000000000000000000000000000000000000..8f25a4a8f8ad99491591386dd15d23be63e4c5e7 --- /dev/null +++ b/examples/demo9/golang/client/model_geo_data.go @@ -0,0 +1,24 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Geographic data +type GeoData struct { + Location *Point `json:"location,omitempty"` + // Optional - Radius (in meters) around the location + Radius float64 `json:"radius,omitempty"` + Path *LineString `json:"path,omitempty"` + // End-of-Path mode:
  • LOOP: When path endpoint is reached, start over from the beginning
  • REVERSE: When path endpoint is reached, return on the reverse path + EopMode string `json:"eopMode,omitempty"` + // Speed of movement along path in m/s + Velocity float64 `json:"velocity,omitempty"` + D2dInRange []string `json:"d2dInRange,omitempty"` + PoaInRange []string `json:"poaInRange,omitempty"` +} diff --git a/examples/demo9/golang/client/model_gpu_config.go b/examples/demo9/golang/client/model_gpu_config.go new file mode 100644 index 0000000000000000000000000000000000000000..ecc9abc8031d368f1257174f00af5d7bc950ef80 --- /dev/null +++ b/examples/demo9/golang/client/model_gpu_config.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// GPU configuration object +type GpuConfig struct { + // Requested GPU type + Type_ string `json:"type,omitempty"` + // Number of GPUs requested + Count int32 `json:"count,omitempty"` +} diff --git a/examples/demo9/golang/client/model_ingress_service.go b/examples/demo9/golang/client/model_ingress_service.go new file mode 100644 index 0000000000000000000000000000000000000000..b611bccb1be991963f4cbd2e65783effb6ed393c --- /dev/null +++ b/examples/demo9/golang/client/model_ingress_service.go @@ -0,0 +1,22 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Internal service exposed externally via specific port +type IngressService struct { + // Service name (unique or multi-edge) + Name string `json:"name,omitempty"` + // Internal service port number + Port int32 `json:"port,omitempty"` + // Externally-exposed unique service port in range (30000 - 32767) + ExternalPort int32 `json:"externalPort,omitempty"` + // Service protocol (TCP or UDP) + Protocol string `json:"protocol,omitempty"` +} diff --git a/examples/demo9/golang/client/model_line_string.go b/examples/demo9/golang/client/model_line_string.go new file mode 100644 index 0000000000000000000000000000000000000000..e09207250ac22d52f5a3ccbf48b380e834cfb057 --- /dev/null +++ b/examples/demo9/golang/client/model_line_string.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// An array of two or more positions in coordinate space (GeoJSON); a position is an array of two numbers +type LineString struct { + // Must be LineString + Type_ string `json:"type"` + // For a LineString, coordinates is an array of two or more positions; a position is an array of two decimal numbers (longitude and latitude precisely in that order) + Coordinates [][]float64 `json:"coordinates,omitempty"` +} diff --git a/examples/demo9/golang/client/model_memory_config.go b/examples/demo9/golang/client/model_memory_config.go new file mode 100644 index 0000000000000000000000000000000000000000..67f375533f18a1bb6ba2472fd60706a25ef80d9c --- /dev/null +++ b/examples/demo9/golang/client/model_memory_config.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Memory configuration object +type MemoryConfig struct { + // Minimum requested memory + Min int32 `json:"min,omitempty"` + // Maximum requested memory + Max int32 `json:"max,omitempty"` +} diff --git a/examples/demo9/golang/client/model_namespace.go b/examples/demo9/golang/client/model_namespace.go new file mode 100644 index 0000000000000000000000000000000000000000..8cb1609630b08d91a08da1c5e781710808e97089 --- /dev/null +++ b/examples/demo9/golang/client/model_namespace.go @@ -0,0 +1,16 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + package client + + // Namespace object + type Namespace struct { + // Name of the Sandbox + Sandbox_name string `json:"sandbox_name,omitempty"` + } \ No newline at end of file diff --git a/examples/demo9/golang/client/model_network_characteristics.go b/examples/demo9/golang/client/model_network_characteristics.go new file mode 100644 index 0000000000000000000000000000000000000000..72a1eb452cf9dd45cf4ab501d37092246c38f9ce --- /dev/null +++ b/examples/demo9/golang/client/model_network_characteristics.go @@ -0,0 +1,28 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Network characteristics object +type NetworkCharacteristics struct { + // Latency in ms + Latency int32 `json:"latency,omitempty"` + // Latency variation in ms + LatencyVariation int32 `json:"latencyVariation,omitempty"` + // Latency distribution. Can only be set in the Scenario Deployment network characteristics, ignored otherwise. Latency distribution is set for the whole network and applied to every end-to-end traffic flows. Default value is 'Normal' distribution. + LatencyDistribution string `json:"latencyDistribution,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by throughputUl and throughputDl + Throughput int32 `json:"throughput,omitempty"` + // Downlink throughput limit in Mbps + ThroughputDl int32 `json:"throughputDl,omitempty"` + // Uplink throughput limit in Mbps + ThroughputUl int32 `json:"throughputUl,omitempty"` + // Packet loss percentage + PacketLoss float64 `json:"packetLoss,omitempty"` +} diff --git a/examples/demo9/golang/client/model_network_location.go b/examples/demo9/golang/client/model_network_location.go new file mode 100644 index 0000000000000000000000000000000000000000..e6987765ff7f2d2a9332c5ae37ca1f5436bd00f7 --- /dev/null +++ b/examples/demo9/golang/client/model_network_location.go @@ -0,0 +1,39 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Logical network location object +type NetworkLocation struct { + // Unique network location ID + Id string `json:"id,omitempty"` + // Network location name + Name string `json:"name,omitempty"` + // Network location type + Type_ string `json:"type,omitempty"` + NetChar *NetworkCharacteristics `json:"netChar,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latency + TerminalLinkLatency int32 `json:"terminalLinkLatency,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation + TerminalLinkLatencyVariation int32 `json:"terminalLinkLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl + TerminalLinkThroughput int32 `json:"terminalLinkThroughput,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss + TerminalLinkPacketLoss float64 `json:"terminalLinkPacketLoss,omitempty"` + // Key/Value Pair Map (string, string) + Meta map[string]string `json:"meta,omitempty"` + // Key/Value Pair Map (string, string) + UserMeta map[string]string `json:"userMeta,omitempty"` + CellularPoaConfig *CellularPoaConfig `json:"cellularPoaConfig,omitempty"` + Poa4GConfig *Poa4GConfig `json:"poa4GConfig,omitempty"` + Poa5GConfig *Poa5GConfig `json:"poa5GConfig,omitempty"` + PoaWifiConfig *PoaWifiConfig `json:"poaWifiConfig,omitempty"` + GeoData *GeoData `json:"geoData,omitempty"` + PhysicalLocations []PhysicalLocation `json:"physicalLocations,omitempty"` +} diff --git a/examples/demo9/golang/client/model_oauth.go b/examples/demo9/golang/client/model_oauth.go new file mode 100644 index 0000000000000000000000000000000000000000..76070524aa3fa66d8cb967606b47da1064a7045e --- /dev/null +++ b/examples/demo9/golang/client/model_oauth.go @@ -0,0 +1,17 @@ +/* + * MEC Oauth API + * + * The MEC Oauth API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Oauth object +type Oauth struct { + // Oauth + User_code string `json:"user_code,omitempty"` + Verification_uri string `json:"verification_uri,omitempty"` +} diff --git a/examples/demo9/golang/client/model_physical_location.go b/examples/demo9/golang/client/model_physical_location.go new file mode 100644 index 0000000000000000000000000000000000000000..93ecd86bf5f10f3bfd636d054bf2e7f21a7b5a72 --- /dev/null +++ b/examples/demo9/golang/client/model_physical_location.go @@ -0,0 +1,47 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Physical location object +type PhysicalLocation struct { + // Unique physical location ID + Id string `json:"id,omitempty"` + // Physical location name + Name string `json:"name,omitempty"` + // Physical location type + Type_ string `json:"type,omitempty"` + // true: Physical location is external to MEEP false: Physical location is internal to MEEP + IsExternal bool `json:"isExternal,omitempty"` + GeoData *GeoData `json:"geoData,omitempty"` + NetworkLocationsInRange []string `json:"networkLocationsInRange,omitempty"` + // true: Physical location has network connectivity false: Physical location has no network connectivity + Connected bool `json:"connected,omitempty"` + // true: Physical location uses a wireless connection false: Physical location uses a wired connection + Wireless bool `json:"wireless,omitempty"` + // Prioritized, comma-separated list of supported wireless connection types. Default priority if not specififed is 'wifi,5g,4g,other'. Wireless connection types: - 4g - 5g - wifi - other + WirelessType string `json:"wirelessType,omitempty"` + DataNetwork *DnConfig `json:"dataNetwork,omitempty"` + // Key/Value Pair Map (string, string) + Meta map[string]string `json:"meta,omitempty"` + // Key/Value Pair Map (string, string) + UserMeta map[string]string `json:"userMeta,omitempty"` + Processes []Process `json:"processes,omitempty"` + NetChar *NetworkCharacteristics `json:"netChar,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latency + LinkLatency int32 `json:"linkLatency,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation + LinkLatencyVariation int32 `json:"linkLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl + LinkThroughput int32 `json:"linkThroughput,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss + LinkPacketLoss float64 `json:"linkPacketLoss,omitempty"` + // Physical location MAC Address + MacId string `json:"macId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_poa4_g_config.go b/examples/demo9/golang/client/model_poa4_g_config.go new file mode 100644 index 0000000000000000000000000000000000000000..57656d41dfe6544274d80ff810ed8b30143d3833 --- /dev/null +++ b/examples/demo9/golang/client/model_poa4_g_config.go @@ -0,0 +1,16 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Cellular 4G POA configuration information +type Poa4GConfig struct { + // The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell + CellId string `json:"cellId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_poa5_g_config.go b/examples/demo9/golang/client/model_poa5_g_config.go new file mode 100644 index 0000000000000000000000000000000000000000..19fbd133470a1c313b0487ac30ddcdd6b2167f0b --- /dev/null +++ b/examples/demo9/golang/client/model_poa5_g_config.go @@ -0,0 +1,16 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Cellular 5G POA configuration information +type Poa5GConfig struct { + // The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the NR serving the cell + CellId string `json:"cellId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_poa_wifi_config.go b/examples/demo9/golang/client/model_poa_wifi_config.go new file mode 100644 index 0000000000000000000000000000000000000000..1f20248b73f846064c900bef226e01d88dada674 --- /dev/null +++ b/examples/demo9/golang/client/model_poa_wifi_config.go @@ -0,0 +1,16 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// WIFI POA configuration information +type PoaWifiConfig struct { + // WIFI POA MAC Address + MacId string `json:"macId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_point.go b/examples/demo9/golang/client/model_point.go new file mode 100644 index 0000000000000000000000000000000000000000..df9b5677e1c8ce8981175aa9c6ccdac7005f8b18 --- /dev/null +++ b/examples/demo9/golang/client/model_point.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// A single position in coordinate space (GeoJSON); a position is an array of two numbers +type Point struct { + // Must be Point + Type_ string `json:"type"` + // For a Point, coordinates MUST be an array of two decimal numbers; longitude and latitude precisely in that order + Coordinates []float64 `json:"coordinates,omitempty"` +} diff --git a/examples/demo9/golang/client/model_problem_details.go b/examples/demo9/golang/client/model_problem_details.go new file mode 100644 index 0000000000000000000000000000000000000000..b3b825a43b6c87be4b25fa304df8a8d7abba1926 --- /dev/null +++ b/examples/demo9/golang/client/model_problem_details.go @@ -0,0 +1,24 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// The definition of the general \"ProblemDetails\" data structure from IETF RFC 7807 is reproduced inthis structure. Compared to the general framework defined in IETF RFC 7807, the \"status\" and \"detail\" attributes are mandated to be included by the present document, to ensure that the response contains additional textual information about an error. IETF RFC 7807 foresees extensibility of the \"ProblemDetails\" type. It is possible that particular APIs in the present document, or particular implementations, define extensions to define additional attributes that provide more information about the error. The description column only provides some explanation of the meaning to Facilitate understanding of the design. For a full description, see IETF RFC 7807. +type ProblemDetails struct { + // A URI reference according to IETF RFC 3986 that identifies the problem type. It is encouraged that the URI provides human-readable documentation for the problem (e.g. using HTML) when dereferenced. When this member is not present, its value is assumed to be \"about:blank\". + Type_ string `json:"type,omitempty"` + // A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization. If type is given and other than \"about:blank\", this attribute shall also be provided. A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4). + Title string `json:"title,omitempty"` + // The HTTP status code for this occurrence of the problem. The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. + Status int32 `json:"status"` + // A human-readable explanation specific to this occurrence of the problem. + Detail string `json:"detail"` + // A URI reference that identifies the specific occurrence of the problem. It may yield further information if dereferenced. + Instance string `json:"instance,omitempty"` +} diff --git a/examples/demo9/golang/client/model_process.go b/examples/demo9/golang/client/model_process.go new file mode 100644 index 0000000000000000000000000000000000000000..317b6cd427bf217959ceec447c572e9c1b74670f --- /dev/null +++ b/examples/demo9/golang/client/model_process.go @@ -0,0 +1,58 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Application or service object +type Process struct { + // Unique process ID + Id string `json:"id,omitempty"` + // Process name + Name string `json:"name,omitempty"` + // Process type + Type_ string `json:"type,omitempty"` + // true: process is external to MEEP false: process is internal to MEEP + IsExternal bool `json:"isExternal,omitempty"` + // Docker image to deploy inside MEEP + Image string `json:"image,omitempty"` + // Environment variables using the format NAME=\"value\",NAME=\"value\",NAME=\"value\" + Environment string `json:"environment,omitempty"` + // Arguments to command executable + CommandArguments string `json:"commandArguments,omitempty"` + // Executable to invoke at container start up + CommandExe string `json:"commandExe,omitempty"` + ServiceConfig *ServiceConfig `json:"serviceConfig,omitempty"` + GpuConfig *GpuConfig `json:"gpuConfig,omitempty"` + MemoryConfig *MemoryConfig `json:"memoryConfig,omitempty"` + CpuConfig *CpuConfig `json:"cpuConfig,omitempty"` + ExternalConfig *ExternalConfig `json:"externalConfig,omitempty"` + // Process status + Status string `json:"status,omitempty"` + // Chart location for the deployment of the chart provided by the user + UserChartLocation string `json:"userChartLocation,omitempty"` + // Chart values.yaml file location for the deployment of the chart provided by the user + UserChartAlternateValues string `json:"userChartAlternateValues,omitempty"` + // Chart supplemental information related to the group (service) + UserChartGroup string `json:"userChartGroup,omitempty"` + // Key/Value Pair Map (string, string) + Meta map[string]string `json:"meta,omitempty"` + // Key/Value Pair Map (string, string) + UserMeta map[string]string `json:"userMeta,omitempty"` + NetChar *NetworkCharacteristics `json:"netChar,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latency + AppLatency int32 `json:"appLatency,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation + AppLatencyVariation int32 `json:"appLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl + AppThroughput int32 `json:"appThroughput,omitempty"` + // **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss + AppPacketLoss float64 `json:"appPacketLoss,omitempty"` + // Identifier used for process placement in AdvantEDGE cluster + PlacementId string `json:"placementId,omitempty"` +} diff --git a/examples/demo9/golang/client/model_sandbox_app_instances.go b/examples/demo9/golang/client/model_sandbox_app_instances.go new file mode 100644 index 0000000000000000000000000000000000000000..24db2e45792df4a56bb33ca42bffc79964014446 --- /dev/null +++ b/examples/demo9/golang/client/model_sandbox_app_instances.go @@ -0,0 +1,15 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +type SandboxAppInstances struct { + // The application instance identifier. + Id string `json:"id"` +} diff --git a/examples/demo9/golang/client/model_sandbox_logs_subscriptions.go b/examples/demo9/golang/client/model_sandbox_logs_subscriptions.go new file mode 100644 index 0000000000000000000000000000000000000000..a4e521306353277e5d9bef867c89a37ce8aef5e2 --- /dev/null +++ b/examples/demo9/golang/client/model_sandbox_logs_subscriptions.go @@ -0,0 +1,17 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +type SandboxLogsSubscriptions struct { + // The callback to notify log messages. + CallbackReference string `json:"callbackReference"` + // The reference of the subscription. + SubscriptionReference string `json:"subscriptionReference,omitempty"` +} diff --git a/examples/demo9/golang/client/model_sandbox_mec_services.go b/examples/demo9/golang/client/model_sandbox_mec_services.go new file mode 100644 index 0000000000000000000000000000000000000000..32dc78ea05a66e218758d09c2895cd7991e69b72 --- /dev/null +++ b/examples/demo9/golang/client/model_sandbox_mec_services.go @@ -0,0 +1,17 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +type SandboxMecServices struct { + // The MEC service name. + Id string `json:"id"` + // When a MEC service is selected, this field contains a token which shall be used in MEC service API URI. + ServiceId string `json:"service_id,omitempty"` +} diff --git a/examples/demo9/golang/client/model_sandbox_network_scenario.go b/examples/demo9/golang/client/model_sandbox_network_scenario.go new file mode 100644 index 0000000000000000000000000000000000000000..4c0fac6bae6001294da4ad37fcfc20d8d4856909 --- /dev/null +++ b/examples/demo9/golang/client/model_sandbox_network_scenario.go @@ -0,0 +1,15 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +type SandboxNetworkScenario struct { + // The network scenario name. + Id string `json:"id"` +} diff --git a/examples/demo9/golang/client/model_scenario.go b/examples/demo9/golang/client/model_scenario.go new file mode 100644 index 0000000000000000000000000000000000000000..913dacfcebf438f97093e03ceb72a46a22886d54 --- /dev/null +++ b/examples/demo9/golang/client/model_scenario.go @@ -0,0 +1,24 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Scenario object +type Scenario struct { + // Scenario version + Version string `json:"version,omitempty"` + // Unique scenario ID + Id string `json:"id,omitempty"` + // Unique scenario name + Name string `json:"name,omitempty"` + // User description of the scenario. + Description string `json:"description,omitempty"` + Config *ScenarioConfig `json:"config,omitempty"` + Deployment *Deployment `json:"deployment,omitempty"` +} diff --git a/examples/demo9/golang/client/model_scenario_config.go b/examples/demo9/golang/client/model_scenario_config.go new file mode 100644 index 0000000000000000000000000000000000000000..e23a0529febdf7e889c3e0af3c524b07b4e2f929 --- /dev/null +++ b/examples/demo9/golang/client/model_scenario_config.go @@ -0,0 +1,18 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Scenario configuration +type ScenarioConfig struct { + // Visualization configuration + Visualization string `json:"visualization,omitempty"` + // Other scenario configuration + Other string `json:"other,omitempty"` +} diff --git a/examples/demo9/golang/client/model_service_config.go b/examples/demo9/golang/client/model_service_config.go new file mode 100644 index 0000000000000000000000000000000000000000..b3016e873210224bd0bb6017eb0e69e6727d5b75 --- /dev/null +++ b/examples/demo9/golang/client/model_service_config.go @@ -0,0 +1,19 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Service object +type ServiceConfig struct { + // Unique service name + Name string `json:"name,omitempty"` + // Multi-Edge service name, if any + MeSvcName string `json:"meSvcName,omitempty"` + Ports []ServicePort `json:"ports,omitempty"` +} diff --git a/examples/demo9/golang/client/model_service_port.go b/examples/demo9/golang/client/model_service_port.go new file mode 100644 index 0000000000000000000000000000000000000000..1b04cf89752f2274462c184c7b5aa2ecf476de9e --- /dev/null +++ b/examples/demo9/golang/client/model_service_port.go @@ -0,0 +1,20 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Service port object +type ServicePort struct { + // Protocol that the application is using (TCP or UDP) + Protocol string `json:"protocol,omitempty"` + // Port number that the service is listening on + Port int32 `json:"port,omitempty"` + // External port number on which to expose the application (30000 - 32767)
  • Only one application allowed per external port
  • Scenario builder must configure to prevent conflicts + ExternalPort int32 `json:"externalPort,omitempty"` +} diff --git a/examples/demo9/golang/client/model_ue.go b/examples/demo9/golang/client/model_ue.go new file mode 100644 index 0000000000000000000000000000000000000000..5574dae9b5a90fac49987c0e84f137d6cdd4fe70 --- /dev/null +++ b/examples/demo9/golang/client/model_ue.go @@ -0,0 +1,17 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +type Ue struct { + // The UE name. + Id string `json:"id"` + // The number of UE instance. + Count int32 `json:"count,omitempty"` +} diff --git a/examples/demo9/golang/client/model_zone.go b/examples/demo9/golang/client/model_zone.go new file mode 100644 index 0000000000000000000000000000000000000000..fb095e82ebad615a43b9ccf2e24d7f9ae5e3c32d --- /dev/null +++ b/examples/demo9/golang/client/model_zone.go @@ -0,0 +1,50 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.9 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +// Logical zone (MEC network) object +type Zone struct { + // Unique zone ID + Id string `json:"id,omitempty"` + // Zone name + Name string `json:"name,omitempty"` + // Zone type + Type_ string `json:"type,omitempty"` + NetChar *NetworkCharacteristics `json:"netChar,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterFogLatency int32 `json:"interFogLatency,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterFogLatencyVariation int32 `json:"interFogLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterFogThroughput int32 `json:"interFogThroughput,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterFogPacketLoss float64 `json:"interFogPacketLoss,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterEdgeLatency int32 `json:"interEdgeLatency,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterEdgeLatencyVariation int32 `json:"interEdgeLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterEdgeThroughput int32 `json:"interEdgeThroughput,omitempty"` + // **DEPRECATED** As of release 1.3.0, no longer supported + InterEdgePacketLoss float64 `json:"interEdgePacketLoss,omitempty"` + // **DEPRECATED** As of release 1.3.0, replaced by netChar latency + EdgeFogLatency int32 `json:"edgeFogLatency,omitempty"` + // **DEPRECATED** As of release 1.3.0, replaced by netChar latencyVariation + EdgeFogLatencyVariation int32 `json:"edgeFogLatencyVariation,omitempty"` + // **DEPRECATED** As of release 1.3.0, replaced by netChar throughput + EdgeFogThroughput int32 `json:"edgeFogThroughput,omitempty"` + // **DEPRECATED** As of release 1.3.0, replaced by netChar packetLoss + EdgeFogPacketLoss float64 `json:"edgeFogPacketLoss,omitempty"` + // Key/Value Pair Map (string, string) + Meta map[string]string `json:"meta,omitempty"` + // Key/Value Pair Map (string, string) + UserMeta map[string]string `json:"userMeta,omitempty"` + NetworkLocations []NetworkLocation `json:"networkLocations,omitempty"` +} diff --git a/examples/demo9/golang/client/response.go b/examples/demo9/golang/client/response.go new file mode 100644 index 0000000000000000000000000000000000000000..13f25177a99aef78932be86f06ac089531fdbe58 --- /dev/null +++ b/examples/demo9/golang/client/response.go @@ -0,0 +1,43 @@ +/* + * MEC Sandbox API + * + * The MEC Sandbox API described using OpenAPI + * + * API version: 0.0.2 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package client + +import ( + "net/http" +) + +type APIResponse struct { + *http.Response `json:"-"` + Message string `json:"message,omitempty"` + // Operation is the name of the swagger operation. + Operation string `json:"operation,omitempty"` + // RequestURL is the request URL. This value is always available, even if the + // embedded *http.Response is nil. + RequestURL string `json:"url,omitempty"` + // Method is the HTTP method used for the request. This value is always + // available, even if the embedded *http.Response is nil. + Method string `json:"method,omitempty"` + // Payload holds the contents of the response body (which may be nil or empty). + // This is provided here as the raw response.Body() reader will have already + // been drained. + Payload []byte `json:"-"` +} + +func NewAPIResponse(r *http.Response) *APIResponse { + + response := &APIResponse{Response: r} + return response +} + +func NewAPIResponseWithError(errorMessage string) *APIResponse { + + response := &APIResponse{Message: errorMessage} + return response +} diff --git a/examples/demo9/golang/docker_build.sh b/examples/demo9/golang/docker_build.sh new file mode 100755 index 0000000000000000000000000000000000000000..c93101ee2c0f62406e0ad45f631584a44434b0eb --- /dev/null +++ b/examples/demo9/golang/docker_build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e +set -x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin/demo9 + +docker pull golang +docker run --rm -it -v$PWD:/opt/local/etsi/demo9 golang bash -c "cd /opt/local/etsi/demo9 && ./build-demo9.sh && chown -R $UID:$UID ./bin" + +echo "" +echo ">>> Demo Service build completed" diff --git a/examples/demo9/golang/docker_run.sh b/examples/demo9/golang/docker_run.sh new file mode 100755 index 0000000000000000000000000000000000000000..f0193e5ca2aa338c3902a2f389e2414b121283bb --- /dev/null +++ b/examples/demo9/golang/docker_run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e +set +x + +docker pull golang +docker run --rm -it -v$PWD:/opt/local/etsi/demo9 -v$HOME/var:/opt/local/etsi/var golang bash -c "cd /opt/local/etsi/demo9/bin && ./demo9 ../app_instance.yaml" + +echo "" +echo ">>> Done" diff --git a/examples/demo9/golang/dockerize.sh b/examples/demo9/golang/dockerize.sh new file mode 100755 index 0000000000000000000000000000000000000000..6817d99a604abc4310af60150084febdfaae5f9c --- /dev/null +++ b/examples/demo9/golang/dockerize.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e +set +x + +# Get full path to script directory +SCRIPT=$(readlink -f "$0") +BASEDIR=$(dirname "$SCRIPT") + +DEMOBIN=$BASEDIR/bin + +echo "" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo ">>> Dockerizing Demo9 Server" +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +echo "" + +# Copy Dockerfile & config to bin folder +cp $BASEDIR/Dockerfile $DEMOBIN +# cp $BASEDIR/src/backend/app_instance.yaml $DEMOBIN +cp $BASEDIR/entrypoint.sh $DEMOBIN + +echo ">>> Dockerizing" +cd $DEMOBIN +docker build --no-cache --rm -t meep-docker-registry:30001/demo9 . +docker push meep-docker-registry:30001/demo9 +cd $BASEDIR + +echo "" +echo ">>> Done" diff --git a/examples/demo9/golang/entrypoint.sh b/examples/demo9/golang/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..c17384fc72c4bd861a6d130328c0eecaf81a6ef3 --- /dev/null +++ b/examples/demo9/golang/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +echo "mode: advantedge" >app_instance.yaml +echo "sandbox:" >>app_instance.yaml +echo "mecplatform: ${MEEP_MEP_NAME}" >>app_instance.yaml +echo "appid:" ${MEEP_APP_ID} >>app_instance.yaml +echo "localurl: ${MEEP_POD_NAME}" >>app_instance.yaml +echo "port:" >>app_instance.yaml + +# Start service +exec /demo9 ./app_instance.yaml diff --git a/examples/demo9/golang/git_push.sh b/examples/demo9/golang/git_push.sh new file mode 100755 index 0000000000000000000000000000000000000000..ae01b182ae9eb047d0999a496b060e62d7b01e5c --- /dev/null +++ b/examples/demo9/golang/git_push.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/examples/demo9/golang/go.mod b/examples/demo9/golang/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..c5c3d91b8fdafd75f1dff734cf179c7ff526699a --- /dev/null +++ b/examples/demo9/golang/go.mod @@ -0,0 +1,27 @@ +module github.com/InterDigitalInc/AdvantEDGE/example/demo9 + +go 1.18 + +require ( + github.com/google/uuid v1.3.0 + github.com/gorilla/handlers v1.5.1 + github.com/gorilla/mux v1.8.0 + github.com/roymx/viper v1.3.3-0.20190416163942-b9a223fc58a3 + golang.org/x/oauth2 v0.19.0 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/magiconair/properties v1.8.0 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/pelletier/go-toml v1.2.0 // indirect + github.com/spf13/afero v1.1.2 // indirect + github.com/spf13/cast v1.3.0 // indirect + github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/pflag v1.0.3 // indirect + golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a // indirect + golang.org/x/text v0.3.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect +) diff --git a/examples/demo9/golang/go.sum b/examples/demo9/golang/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..e3b299b4dd3df08b0bcaa51d81f80a704223fe7c --- /dev/null +++ b/examples/demo9/golang/go.sum @@ -0,0 +1,52 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/roymx/viper v1.3.3-0.20190416163942-b9a223fc58a3 h1:lBNvYUFo7d4fHs8BXUmoTzbdUo4usq6PlP5qn894sGA= +github.com/roymx/viper v1.3.3-0.20190416163942-b9a223fc58a3/go.mod h1:jo59Sv6xirZtbxbaZbCtrQd1CSufmcxJZIC8hm2tepw= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/examples/demo9/golang/main.go b/examples/demo9/golang/main.go new file mode 100644 index 0000000000000000000000000000000000000000..2a187dcd5c46545b2c88761e582028700e1082df --- /dev/null +++ b/examples/demo9/golang/main.go @@ -0,0 +1,1604 @@ +/* + * Copyright (c) 2025 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * MEC Demo9 API + * + * Demo9 is a MEC application to illustrate the usage of the MEC Sandbox command line APIs. + * + * API version: 0.0.1 + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package main + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "os/signal" + "path/filepath" + "regexp" + "strconv" + "strings" + "syscall" + "time" + + client "github.com/InterDigitalInc/AdvantEDGE/example/demo9/client" + + "github.com/google/uuid" + "github.com/gorilla/handlers" + "github.com/roymx/viper" +) + +type Config struct { + Mode string `mapstructure:"mode"` + SandboxUrl string `mapstructure:"sandbox"` + HttpsOnly bool `mapstructure:"https"` + MecPlatform string `mapstructure:"mecplatform"` + SandboxName string `mapstructure:"sandboxname"` + AppInstanceId string `mapstructure:"appid"` + Localurl string `mapstructure:"localurl"` + Port string `mapstructure:"port"` + CallbackUrl string `mapstructure:"callbackUrl"` + CallbackPort string `mapstructure:"callbackPort"` +} + +// MEC 011 registration +// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.2.6 Type: AppInfo +type AppInfo struct { + AppName string `json:"appName"` // Name of the application. It shall be consistent with the appName in the AppD, if an AppD is available. + AppProvider string `json:"appProvider,omitempty"` // Provider of the application. It shall be consistent with the appProvider in the AppD, if an AppD is available + AppDId string `json:"appDId,omitempty"` // The application descriptor identifier. It is managed by the application provider to identify the application descriptor in a globally unique way + AppInstanceId string `json:"appInstanceId,omitempty"` // Identifier of the application instance. Shall be present if the application instance is instantiated by the MEC Management + IsInsByMec bool `json:"isInsByMec,omitempty"` // Indicate whether the application instance is instantiated by the MEC Management. Default to FALSE if absent. +} + +// MEC 011 ServiceInfo +// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.2 Type: ServiceInfo +type ServiceInfo struct { + SerInstanceId string `json:"serInstanceId,omitempty"` + SerName string `json:"serName"` + SerCategory *CategoryRef `json:"serCategory,omitempty"` + Version string `json:"version"` // Service version + State string `json:"state"` + TransportInfo TransportInfo `json:"transportInfo"` + Serializer string `json:"serializer"` + Links *Links `json:"_links,omitempty"` +} +type CategoryRef struct { + Href string `json:"href"` + Id string `json:"id"` + Name string `json:"name"` + Version string `json:"version"` +} +type TransportInfo struct { + Id string `json:"id"` + Name string `json:"name"` + Type_ *string `json:"type"` + Protocol string `json:"protocol"` + Version string `json:"version"` + SecurityInfo *SecurityInfo `json:"securityInfo"` + Endpoint *OneOfTransportInfoEndpoint `json:"endpoint"` +} +type SecurityInfo struct { + OAuth2Info *SecurityInfoOAuth2Info `json:"oAuth2Info,omitempty"` +} +type SecurityInfoOAuth2Info struct { + GrantTypes []string `json:"grantTypes"` + TokenEndpoint string `json:"tokenEndpoint"` +} +type OneOfTransportInfoEndpoint struct { + EndPointInfoUris + EndPointInfoFqdn + EndPointInfoAddresses + EndPointInfoAlternative +} +type EndPointInfoUris struct { + Uris []string `json:"uris"` +} +type EndPointInfoFqdn struct { + Fqdn []string `json:"fqdn"` +} +type EndPointInfoAddress struct { + Host string `json:"host"` + Port int32 `json:"port"` +} +type EndPointInfoAddresses struct { + Addresses []EndPointInfoAddress `json:"addresses"` +} +type EndPointInfoAlternative struct { + Alternative *interface{} `json:"alternative"` +} + +// MEC 011 Termination subscription +// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.3.2 Type: AppTerminationNotificationSubscription +type AppTerminationNotificationSubscription struct { + SubscriptionType string `json:"subscriptionType"` + CallbackReference string `json:"callbackReference"` + Links *Self `json:"_links"` + AppInstanceId string `json:"appInstanceId"` +} + +// MEC 033 DeviceInfo +// ETSI GS MEC 033 V3.1.1 (2024-04) Clause 8.1.2.2 Type: DeviceInfo +type IotPlatformInfo struct { + IotPlatformId string `json:"iotPlatformId"` + UserTransportInfo []MbTransportInfo `json:"userTransportInfo"` + CustomServicesTransportInfo []TransportInfo `json:"customServicesTransportInfo,omitempty"` + Enabled bool `json:"enabled"` +} + +type MbTransportInfo struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Type_ *string `json:"type"` + Protocol string `json:"protocol"` + Version string `json:"version"` + Endpoint *EndPointInfo `json:"endpoint"` + Security *SecurityInfo `json:"security"` + ImplSpecificInfo *ImplSpecificInfo `json:"implSpecificInfo"` +} + +type EndPointInfo struct { + Uris []string `json:"uris,omitempty"` + Fqdn []string `json:"fqdn,omitempty"` + Addresses []Addresses `json:"addresses,omitempty"` + Alternative string `json:"alternative,omitempty"` +} + +type Addresses struct { + Host string `json:"host"` + Port int32 `json:"port"` +} + +type OAuth2Info struct { + GrantTypes []string `json:"grantTypes"` + TokenEndpoint string `json:"tokenEndpoint,omitempty"` +} + +type ImplSpecificInfo struct { + EventTopics []string `json:"eventTopics,omitempty"` + UplinkTopics []string `json:"uplinkTopics,omitempty"` + DownlinkTopics []string `json:"downlinkTopics,omitempty"` +} + +type TrafficRuleDescriptor struct { + TrafficRuleId string `json:"trafficRuleId"` + FilterType string `json:"filterType"` + Priority int32 `json:"priority"` + TrafficFilter []TrafficFilter `json:"trafficFilter"` + Action string `json:"action"` + DstInterface *InterfaceDescriptor `json:"dstInterface,omitempty"` +} + +type InterfaceDescriptor struct { + InterfaceType string `json:"interfaceType"` + TunnelInfo *TunnelInfo `json:"tunnelInfo,omitempty"` + SrcMACAddress string `json:"srcMACAddress,omitempty"` + DstMACAddress string `json:"dstMACAddress,omitempty"` + DstIPAddress string `json:"dstIPAddress,omitempty"` +} + +type TunnelInfo struct { + TunnelType string `json:"tunnelType"` + TunnelDstAddress string `json:"tunnelDstAddress"` + TunnelSrcAddress string `json:"tunnelSrcAddress"` + TunnelSpecificData string `json:"tunnelSpecificData,omitempty"` +} + +type TrafficFilter struct { + SrcAddress []string `json:"srcAddress,omitempty"` + DstAddress []string `json:"dstAddress,omitempty"` + SrcPort []string `json:"srcPort,omitempty"` + DstPort []string `json:"dstPort,omitempty"` + Protocol []string `json:"protocol,omitempty"` + Tag []string `json:"tag,omitempty"` + Uri []string `json:"uri,omitempty"` + PacketLabel []string `json:"packetLabel,omitempty"` + SrcTunnelAddress []string `json:"srcTunnelAddress,omitempty"` + TgtTunnelAddress []string `json:"tgtTunnelAddress,omitempty"` + SrcTunnelPort []string `json:"srcTunnelPort,omitempty"` + DstTunnelPort []string `json:"dstTunnelPort,omitempty"` + QCI int32 `json:"qCI,omitempty"` + DSCP int32 `json:"dSCP,omitempty"` + TC int32 `json:"tC,omitempty"` +} + +// ETSI GS MEC 033 V3.1.1 (2024-04) Clause 8.1.2.2 Type: DeviceInfo +type DeviceInfo struct { + DeviceAuthenticationInfo string `json:"deviceAuthenticationInfo"` + DeviceMetadata []KeyValuePair `json:"deviceMetadata,omitempty"` + Gpsi string `json:"gpsi,omitempty"` + Pei string `json:"pei,omitempty"` + Supi string `json:"supi,omitempty"` + Msisdn string `json:"msisdn,omitempty"` + Imei string `json:"imei,omitempty"` + Imsi string `json:"imsi,omitempty"` + Iccid string `json:"iccid,omitempty"` + DeviceId string `json:"deviceId"` + //RequestedMecTrafficRule []TrafficRuleDescriptor `json:"requestedMecTrafficRule,omitempty"` + RequestedIotPlatformId string `json:"requestedIotPlatformId,omitempty"` + RequestedUserTransportId string `json:"requestedUserTransportId,omitempty"` + DeviceSpecificMessageFormats *DeviceSpecificMessageFormats `json:"deviceSpecificMessageFormats,omitempty"` + DownlinkInfo *DownlinkInfo `json:"downlinkInfo,omitempty"` + ClientCertificate string `json:"clientCertificate,omitempty"` + Enabled bool `json:"enabled"` +} + +type KeyValuePair struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type DownlinkInfo struct { + DownlinkTopic string `json:"downlinkTopic,omitempty"` + DevicePort int32 `json:"devicePort,omitempty"` +} + +type DeviceSpecificMessageFormats struct { + EventMsgFormat *EventMsg `json:"eventMsgFormat,omitempty"` + UplinkMsgFormat *UplinkMsg `json:"uplinkMsgFormat,omitempty"` +} + +type EventMsg struct { + EventTopic string `json:"eventTopic"` + SelectedSerializer *string `json:"selectedSerializer"` + IncludeDeviceAddr bool `json:"includeDeviceAddr,omitempty"` + IncludeDeviceMetadata bool `json:"includeDeviceMetadata,omitempty"` + IncludePei bool `json:"includePei,omitempty"` + IncludeSupi bool `json:"includeSupi,omitempty"` + IncludeImei bool `json:"includeImei,omitempty"` + IncludeImsi bool `json:"includeImsi,omitempty"` + IncludeIccid bool `json:"includeIccid,omitempty"` + IncludeDeviceId bool `json:"includeDeviceId,omitempty"` +} + +type UplinkMsg struct { + UplinkTopic string `json:"uplinkTopic"` + SelectedSerializer *string `json:"selectedSerializer"` + IncludeDevicePort bool `json:"includeDevicePort,omitempty"` + IncludeDeviceAddr bool `json:"includeDeviceAddr,omitempty"` + IncludeDeviceMetadata bool `json:"includeDeviceMetadata,omitempty"` + IncludePei bool `json:"includePei,omitempty"` + IncludeSupi bool `json:"includeSupi,omitempty"` + IncludeImei bool `json:"includeImei,omitempty"` + IncludeImsi bool `json:"includeImsi,omitempty"` + IncludeIccid bool `json:"includeIccid,omitempty"` + IncludeDeviceId bool `json:"includeDeviceId,omitempty"` +} + +// MEC Common types +type LinkType struct { + // URI referring to a resource + Href string `json:"href,omitempty"` +} +type Links struct { + Self *LinkType `json:"self"` +} +type Self struct { + Self *LinkType `json:"self"` +} +type TimeStamp struct { + Seconds int32 `json:"seconds"` + NanoSeconds int32 `json:"nanoSeconds"` +} + +var ( + dir string + fileName string + provider string = "Jupyter2024" //"github" + run bool = true + done chan bool + cfg *client.Configuration = nil + cl *client.APIClient = nil + reader *bufio.Reader = nil + sandboxName string = "" + verificationUri string = "" + userCode string = "" + mecUrl string = "" + mecPlateform string = "" + callbackUrl string = "" + callbackPort string = "" + terminationSubscriptionID string = "" + scenarios []client.SandboxNetworkScenario + scenario []client.Scenario + scenarioId int + services []client.SandboxMecServices + appsInfo client.ApplicationInfo + isRegistered bool = false + appServiceInfo ServiceInfo + subscriptions []LinkType + iotPlatformInfo IotPlatformInfo + iotDevices []DeviceInfo +) + +// Display menu and read selection +const ( + LOGIN = "l" + LOGOUT = "L" + IOT_PLTF_DISCOVERY = "d" + IOT_DEV_DISCOVERY_BY_ID = "D" + IOT_DEV_DISCOVERY = "v" + IOT_CREATE_DEVICE = "c" + IOT_DELETE_DEVICE = "C" + STATUS = "T" + OM2M_DISCOVERY = "0" + OM2M_DISCOVERY_BY_ID = "1" + OM2M_CREATE_AE = "2" + OM2M_CREATE_CNT = "3" + OM2M_CREATE_CIN = "4" + OM2M_CREATE_SUB = "5" + OM2M_DELETE_SUB = "6" + QUIT = "q" +) + +func clearScreen() { + //fmt.Println("\033[2J") +} + +func menu(message string) []string { + clearScreen() + fmt.Printf( + "Mandatory commands:\n"+ + "\t%s: Login, %s: Logout\n"+ + "Optional commands:\n"+ + "\t%s: Current status:\n"+ + "MEC 033:\n"+ + "\t%s: Full IoT platform discovery, %s : Get IoT platform by IoTPlatformId, %s: Full device discovery\n"+ + "\t%s: Create a device, %s: Delete a device\n"+ + "MEC 046:\n"+ + "\t%s: Full discovery, %s : Get data for a specific oneM2M resource\n"+ + "oneM2M commands:\n"+ + "\t%s: Create an AE, %s: Create a CNT, %s: Create a CIN, %s: Create a subscription, %s: Delete a subscription\n"+ + "%s: Quit\n", + LOGIN, LOGOUT, STATUS, IOT_PLTF_DISCOVERY, IOT_DEV_DISCOVERY_BY_ID, IOT_DEV_DISCOVERY, IOT_CREATE_DEVICE, IOT_DELETE_DEVICE, OM2M_DISCOVERY, OM2M_DISCOVERY_BY_ID, OM2M_CREATE_AE, OM2M_CREATE_CNT, OM2M_CREATE_CIN, OM2M_CREATE_SUB, OM2M_DELETE_SUB, QUIT) + if message != "" { + fmt.Println("Last message: ", message) + } + fmt.Print("Enter your choice: ") + + // Read selection + choice, _ := reader.ReadString('\n') + choice = strings.Trim(strings.Trim(choice, "\n"), " ") + return strings.Split(choice, " ") +} + +func login() (string, string, error) { + fmt.Println(">>> login") + + // Sanity checks + if sandboxName != "" { + return "", "", errors.New("Please, logout first") + } + + // Initialize g;lobal variables + scenarioId = -1 + appsInfo.Id = "" + terminationSubscriptionID = "" + appServiceInfo.SerInstanceId = "" + subscriptions = make([]LinkType, 0) + + sandbox, _, err := cl.AuthorizationApi.Login(context.TODO(), provider) + if err != nil { + return "", "", err + } + fmt.Println("login: sandbox: ", sandbox) + + return sandbox.User_code, sandbox.Verification_uri, nil +} + +func getNamespace() (string, error) { + fmt.Println(">>> Get Namespace") + + response, _, err := cl.AuthorizationApi.GetNamespace(context.TODO(), userCode) + if err != nil { + return "", err + } + fmt.Println("login: Namespace is: ", response) + sandboxName = response.Sandbox_name + + return sandboxName, nil +} + +func logout() error { + fmt.Println(">>> logout: ", sandboxName) + + // Sanity check + if sandboxName == "" { + err := errors.New("logout: Wrong parameters") + return err + } + + if len(iotDevices) != 0 { + for _, d := range iotDevices { + mec033_om2m_delete_device(d.DeviceId) + } // End of 'for' statement + } + + // Delete subscriptions done + if len(subscriptions) != 0 { + for _, l := range subscriptions { + delete_subscription(l.Href) + } // End of 'for' statement + } + + // Delete subscription if any + if terminationSubscriptionID != "" { + delete_termination_subscription() + } + + // Delete MEC service if any + if appServiceInfo.SerInstanceId != "" { + mec011_delete_service() + } + + // Delete registration if any + if isRegistered { + mec011_send_deregistration() + } + + // Delete created AppInstId if any + if appsInfo.Id != "" { + deleteMECAppInstId() + appsInfo.Id = "" + time.Sleep(2 * time.Second) + } + + if appServiceInfo.SerInstanceId != "" { + mec011_delete_service() + appServiceInfo.SerInstanceId = "" + time.Sleep(2 * time.Second) + } + + // Terminate scenario if any + if scenarioId != -1 { + terminateScenario(scenarios[scenarioId].Id) + scenarioId = -1 + time.Sleep(2 * time.Second) + } + + _, err := cl.AuthorizationApi.Logout(context.TODO(), sandboxName) + if err != nil { + return err + } + + sandboxName = "" + + return nil +} + +func getListOfScenarios() ([]client.SandboxNetworkScenario, error) { + fmt.Println(">>> getListOfScenarios") + + scenarios, _, err := cl.SandboxNetworkScenariosApi.SandboxNetworkScenariosGET(context.TODO(), sandboxName) + if err != nil { + fmt.Println("getListOfScenarios: ", err.Error()) + return nil, err + } + fmt.Println("getListOfScenarios: ", scenarios) + + return scenarios, nil +} + +func getScenario(scenarioId string) ([]client.Scenario, error) { + fmt.Println(">>> getScenario: ", scenarioId) + + scenario, _, err := cl.SandboxNetworkScenariosApi.SandboxIndividualNetworkScenariosGET(context.TODO(), sandboxName, scenarioId) + if err != nil { + fmt.Println("getScenario: ", err.Error()) + return nil, err + + } + fmt.Println("scenario: ", scenario) + + return scenario, nil +} + +func activateScenario(scenarioId string) error { + fmt.Println(">>> activateScenario: ", scenarioId) + + // Sanity checks + if sandboxName == "" { + return errors.New("No sandbox available") + } + + _, err := cl.SandboxNetworkScenariosApi.SandboxNetworkScenarioPOST(context.TODO(), sandboxName, scenarioId) + if err != nil { + return err + } + + return nil +} + +func terminateScenario(scenarioId string) error { + fmt.Println(">>> terminateScenario: ", scenarioId) + + // Sanity checks + if sandboxName == "" { + return errors.New("No sandbox available") + } else if scenarioId == "" { + return errors.New("No network scenario available") + } + + _, err := cl.SandboxNetworkScenariosApi.SandboxNetworkScenarioDELETE(context.TODO(), sandboxName, scenarioId) + if err != nil { + return err + } + + return nil +} + +func getListOfMECServices() ([]client.SandboxMecServices, error) { + fmt.Println(">>> getListOfMECServices") + + // Sanity checks + if sandboxName == "" { + return nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return nil, errors.New("No network scenario available") + } + + services, _, err := cl.SandboxMECServicesApi.SandboxMecServicesGET(context.TODO(), sandboxName) + if err != nil { + return nil, err + } + + return services, nil +} + +func getListOfMECAppInstIds() ([]client.ApplicationInfo, error) { + fmt.Println(">>> getListOfMECAppInstIds") + + // Sanity checks + if sandboxName == "" { + return nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return nil, errors.New("No network scenario available") + } + + appsInfos, _, err := cl.SandboxAppInstancesApi.SandboxAppInstancesGET(context.TODO(), sandboxName) + if err != nil { + return nil, err + } + + return appsInfos, nil +} + +func createMECAppInstId(appInfo client.ApplicationInfo) error { + fmt.Println(">>> createMECAppInstId: ", appInfo) + + // Sanity checks + if sandboxName == "" { + return errors.New("No sandbox available") + } else if scenarioId == -1 { + return errors.New("No network scenario available") + } + + _, _, err := cl.SandboxAppInstancesApi.SandboxAppInstancesPOST(context.TODO(), appInfo, sandboxName) + if err != nil { + return err + } + + return nil +} + +func deleteMECAppInstId() error { + fmt.Println(">>> deleteMECAppInstId") + + // Sanity checks + if sandboxName == "" { + return errors.New("No sandbox available") + } else if scenarioId == -1 { + return errors.New("No network scenario available") + } + if appsInfo.Id == "" { + return errors.New("No App instance available") + } + + _, err := cl.SandboxAppInstancesApi.SandboxAppInstancesDELETE(context.TODO(), sandboxName, appsInfo.Id) + if err != nil { + return err + } + + return nil +} + +func verify_idx_len(idx int, len int) (int, error) { + if idx >= len { + return -1, errors.New("Index out of range: " + string(idx)) + } + + return idx, nil +} + +func mec011_send_confirm_ready() (subId string, response *http.Response, err error) { + fmt.Println(">>> mec011_send_confirm_ready") + + // Sanity checks + if sandboxName == "" { + return "", nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return "", nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return "", nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/applications/" + appsInfo.Id + "/confirm_ready" + fmt.Println("mec011_send_confirm_ready: url: " + url) + // Build message body + json_body := "{\"indication\":\"READY\"}" + io_body := strings.NewReader(json_body) + counter := 0 + for { // If pods are not stable and running, the request will fail and we will retry after 2 seconds + // Send request and await response + _, response, err := send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil) + fmt.Println("mec011_send_confirm_ready: confirm ready: " + response.Status) + if err == nil && response.StatusCode == 204 { + break + } + counter++ + if counter >= 10 { + return "", nil, errors.New("Confirm ready failed") + } + time.Sleep(5 * time.Second) + } // End of 'for' statement + + return mec011_send_subscribe_termination() +} + +func mec011_send_subscribe_termination() (subId string, response *http.Response, err error) { + fmt.Println(">>> mec011_send_subscribe_termination") + + // Sanity checks + if sandboxName == "" { + return "", nil, errors.New("No sandbox available") + } else if appsInfo.Id == "" { + return "", nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/applications/" + appsInfo.Id + "/subscriptions" + fmt.Println("mec011_send_subscribe_termination: url: " + url) + // Build message body + appTerminationBody := AppTerminationNotificationSubscription{ + SubscriptionType: "AppTerminationNotificationSubscription", + CallbackReference: callbackUrl + "/asc/termination", + AppInstanceId: appsInfo.Id, + } + json_body, err := json.Marshal(appTerminationBody) + if err != nil { + return "", nil, err + } + fmt.Println("mec011_send_subscribe_termination: json_body: " + string(json_body)) + io_body := bytes.NewReader(json_body) + fmt.Println("mec011_send_subscribe_termination: json_body: ", io_body) + // Send request and await response + _, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil) + if err != nil { + return "", nil, err + } + fmt.Println("mec011_send_subscribe_termination: response: " + response.Status) + fmt.Println("mec011_send_subscribe_termination: Location: " + response.Header["Location"][0]) + + subId, err = extract_subscription_id(url, response.Header["Location"][0]) + if err != nil { + return "", nil, err + } + + return subId, response, nil +} + +func delete_termination_subscription() (err error) { + fmt.Println(">>> delete_termination_subscription") + + // Sanity checks + if sandboxName == "" { + return errors.New("No sandbox available") + } else if scenarioId == -1 { + return errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return errors.New("No App instance available") + } else if terminationSubscriptionID == "" { + return errors.New("No termination subscription") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/applications/" + appsInfo.Id + "/subscriptions/" + terminationSubscriptionID + fmt.Println("delete_termination_subscription: url: " + url) + // Send request and await response + _, _, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil) + if err != nil { + fmt.Println("delete_termination_subscription: " + err.Error()) + return err + } + + terminationSubscriptionID = "" + + return nil +} + +func mec011_send_registration() (body []byte, response *http.Response, err error) { + fmt.Println(">>> mec011_send_registration: ", appsInfo.Name) + fmt.Println(">>> mec011_send_registration: ", appsInfo.Id) + + // Sanity checks + if sandboxName == "" { + return nil, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return nil, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return nil, nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/registrations" + fmt.Println("mec011_send_registration: url: " + url) + // Build message body + appInfo := AppInfo{ + AppName: appsInfo.Name, + AppProvider: "ETSI", + AppDId: uuid.New().String(), + AppInstanceId: appsInfo.Id, + IsInsByMec: true, + } + fmt.Println("mec011_send_registration: appInfo: ", appInfo) + json_body, err := json.Marshal(appInfo) + if err != nil { + return nil, nil, err + } + fmt.Println("mec011_send_registration: json_body: " + string(json_body)) + io_body := bytes.NewReader(json_body) + fmt.Println("mec011_send_registration: io_body: ", io_body) + // Send request and await response + body, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil) + if err != nil { + return nil, nil, err + } + fmt.Println("mec011_send_registration: status: " + response.Status) + fmt.Println("mec011_send_registration: Location: ", response.Header["Location"][0]) + isRegistered = true + + return body, response, nil +} + +func mec011_send_deregistration() (response *http.Response, err error) { + fmt.Println(">>> mec011_send_deregistration") + + // Sanity checks + if sandboxName == "" { + return nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_app_support/v2/registrations/" + appsInfo.Id + fmt.Println("mec011_send_deregistration: url: " + url) + // Send request and await response + _, response, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil) + if err != nil { + return nil, err + } + fmt.Println("mec011_send_deregistration: status: " + response.Status) + + isRegistered = false + + return response, nil +} + +func mec011_create_service() (resId string, response *http.Response, err error) { + fmt.Println(">>> mec011_create_service") + + // Sanity checks + if sandboxName == "" { + return "", nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return "", nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return "", nil, errors.New("No App instance available") + } else if appServiceInfo.SerInstanceId != "" { + return "", nil, errors.New("A MEC service is already created") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/applications/" + appsInfo.Id + "/services" + fmt.Println("mec011_create_service: url: " + url) + // Build message body + transportType := "REST_HTTP" + appServiceInfo = ServiceInfo{ + SerName: "demo9 MEC Service", + SerCategory: &CategoryRef{ + Href: callbackUrl + "/statistic/v1/quantity", + Id: uuid.New().String(), + Name: "Demo", + Version: "1.0.0", + }, + Version: "1.0.0", + State: "ACTIVE", + TransportInfo: TransportInfo{ + Id: uuid.New().String(), + Name: "HTTP REST API", + Type_: &transportType, + Protocol: "HTTP", + Version: "2.0", + SecurityInfo: &SecurityInfo{}, + Endpoint: &OneOfTransportInfoEndpoint{}, + }, + Serializer: "JSON", + } + appServiceInfo.TransportInfo.Endpoint.Uris = append(appServiceInfo.TransportInfo.Endpoint.Uris, callbackUrl+"/statistic/v1/quantity") + json_body, err := json.Marshal(appServiceInfo) + if err != nil { + return "", nil, err + } + fmt.Println("mec011_create_service: json_body: " + string(json_body)) + io_body := bytes.NewReader(json_body) + fmt.Println("mec011_create_service: json_body: ", io_body) + // Send request and await response + body, response, err := send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil) + if err != nil { + return "", nil, err + } + if response.StatusCode != 201 { + return "", nil, errors.New("Invalid Status: " + response.Status) + } + // Unmarshal the response body + fmt.Println("mec011_create_service: body: " + string(body)) + err = json.Unmarshal([]byte(body), &appServiceInfo) + if err != nil { + return "", nil, err + } + + fmt.Println("mec011_create_service: Location: " + response.Header["Location"][0]) + resId, err = extract_subscription_id(url, response.Header["Location"][0]) + if err != nil { + return "", nil, err + } + + return resId, response, nil +} + +func mec011_delete_service() (err error) { + fmt.Println(">>> mec011_delete_service") + + // Sanity checks + if sandboxName == "" { + return errors.New("No sandbox available") + } else if scenarioId == -1 { + return errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return errors.New("No App instance available") + } else if appServiceInfo.SerInstanceId == "" { + return errors.New("No MEC service created") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/applications/" + appsInfo.Id + "/services/" + appServiceInfo.SerInstanceId + fmt.Println("mec011_delete_service: url: " + url) + // Send request and await response + _, _, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil) + if err != nil { + return err + } + + appServiceInfo.SerInstanceId = "" + + return nil +} + +func mec011_get_mec_services() (body []byte, response *http.Response, err error) { + fmt.Println(">>> mec011_get_mec_services") + + // Sanity checks + if sandboxName == "" { + return nil, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return nil, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return nil, nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/services" + fmt.Println("mec011_get_mec_services: url: " + url) + // Send request and await response + body, response, err = send_mec_service_request(http.MethodGet, url, nil, nil, nil, nil) + if err != nil { + return nil, nil, err + } + + return body, nil, nil +} + +func send_mec_service_request(method string, path string, body io.Reader, vars url.Values, queryParams url.Values, location *string) (resbody []byte, res *http.Response, err error) { + fmt.Println(">>> send_mec_service_request: ", appsInfo.Name) + + // Sanity checks + if sandboxName == "" { + err = errors.New("No sandbox available") + return nil, nil, err + } else if scenarioId == -1 { + return nil, nil, errors.New("No network scenario available") + } + + // Setup path and query parameters + url, err := url.Parse(path) + if err != nil { + return nil, nil, err + } + + // Adding Query Param + query := url.Query() + for k, v := range queryParams { + for _, iv := range v { + query.Add(k, iv) + } + } + + // Encode the parameters. + url.RawQuery = query.Encode() + + // Generate a new request + var localVarRequest *http.Request + if body != nil { + localVarRequest, err = http.NewRequest(method, url.String(), body) + } else { + localVarRequest, err = http.NewRequest(method, url.String(), nil) + } + if err != nil { + return nil, nil, err + } + + // Override request host, if applicable + if cfg.Host != "" { + localVarRequest.Host = cfg.Host + } + + headers := http.Header{} + headers.Set("Accept", "application/json") + localVarRequest.Header = headers + + // Add the user agent to the request. + localVarRequest.Header.Add("User-Agent", cfg.UserAgent) + + //fmt.Println("send_mec_service_request: localVarRequest: ", localVarRequest) + + // tr := &http.Transport{ + // TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + // } + // cfg.HTTPClient = &http.Client{Transport: tr} + + res, err = cfg.HTTPClient.Do(localVarRequest) + if err != nil { + return nil, nil, err + } + + resbody, err = ioutil.ReadAll(res.Body) + if err != nil { + return nil, nil, err + } + res.Body.Close() + + return resbody, res, err +} + +func app_status() (resp string) { + resp = "" + if sandboxName != "" { + resp += "Sandbox: " + sandboxName + } + if appsInfo.Id != "" { + resp += ", appsInfo.Id: " + appsInfo.Id + } + if appServiceInfo.SerInstanceId != "" { + resp += ", SerInstanceId: " + appServiceInfo.SerInstanceId + resp += "- uri: " + appServiceInfo.Links.Self.Href + } + if terminationSubscriptionID != "" { + resp += ", Subscription: " + terminationSubscriptionID + } + if isRegistered { + resp += ", Demo9 app registered" + } else { + resp += ", Demo9 app not registered" + } + if len(subscriptions) != 0 { + resp += ", #" + strconv.Itoa(len(subscriptions)) + " subscriptions created" + } else { + resp += ", no subscriptions created" + } + + return resp +} + +func extract_subscription_id(base_url string, subscription_url string) (string, error) { + fmt.Println(">>> extract_subscription_id: base_url: " + base_url) + fmt.Println(">>> extract_subscription_id: subscription_url: " + subscription_url) + + u, _ := url.Parse(base_url) + fmt.Println("extract_subscription_id: subscription_url: " + u.RequestURI()) + re := regexp.MustCompile(u.RequestURI() + "/(?P.+)") + if re == nil { + return "", errors.New("Regexp creation failure") + } + matches := re.FindStringSubmatch(subscription_url) + if matches == nil { + return "", errors.New("Matching failure") + } + + return matches[re.SubexpIndex("sub_id")], nil +} + +func delete_subscription(url string) (response *http.Response, err error) { + fmt.Println(">>> delete_subscription: ", url) + + // Send request and await response + _, response, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil) + if err != nil { + return nil, err + } + + delete_subscription_from_list(url) + + return response, nil +} + +func delete_subscription_from_list(url string) { + fmt.Println(">>> delete_subscription: ", url) + for s, l := range subscriptions { + if l.Href == url { + subscriptions = append(subscriptions[:s], subscriptions[s+1:]...) + break + } + } +} + +func mec033_await_om2m_pltf_registered() (iot_platforms []IotPlatformInfo, response *http.Response, err error) { + fmt.Println(">>> mec033_await_om2m_pltf_registered") + + // Sanity checks + if sandboxName == "" { + return nil, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return nil, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return nil, nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/iots/v1/registered_iot_platforms" + fmt.Println("mec033_await_om2m_pltf_registered: url: " + url) + counter := 0 + for { // If pods are not stable and running, the request will fail and we will retry after 2 seconds + // Send request and await response + var body []byte + body, response, err = send_mec_service_request(http.MethodGet, url, nil, nil, nil, nil) + fmt.Println("mec033_await_om2m_pltf_registered: " + response.Status) + if err == nil && response.StatusCode == 200 { + // Unmarshal the response body + fmt.Println("mec033_await_om2m_pltf_registered: body: " + string(body)) + err = json.Unmarshal([]byte(body), &iot_platforms) + if err != nil { + fmt.Println("mec033_await_om2m_pltf_registered: err: " + err.Error()) + return nil, response, err + } + fmt.Println("mec033_await_om2m_pltf_registered: iot_platforms: " + fmt.Sprint(iot_platforms)) + break + } + counter++ + if counter > 10 { + return nil, nil, errors.New("Timeout waiting for OM2M platform registered") + } + time.Sleep(5 * time.Second) + } + + return iot_platforms, response, nil +} + +func mec033_get_om2m_pltf_by_systemId(iotPlatformId string) (iot_platform IotPlatformInfo, response *http.Response, err error) { + fmt.Println(">>> mec033_get_om2m_pltf_by_systemId: ", iotPlatformId) + + // Sanity checks + if sandboxName == "" { + return iot_platform, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return iot_platform, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return iot_platform, nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/iots/v1/registered_iot_platforms/" + iotPlatformId + fmt.Println("mec033_get_om2m_pltf_by_systemId: url: " + url) + counter := 0 + for { // If pods are not stable and running, the request will fail and we will retry after 2 seconds + // Send request and await response + var body []byte + body, response, err = send_mec_service_request(http.MethodGet, url, nil, nil, nil, nil) + fmt.Println("mec033_get_om2m_pltf_by_systemId: " + response.Status) + if err == nil && response.StatusCode == 200 { + // Unmarshal the response body + fmt.Println("mec033_get_om2m_pltf_by_systemId: body: " + string(body)) + err = json.Unmarshal([]byte(body), &iot_platform) + if err != nil { + fmt.Println("mec033_get_om2m_pltf_by_systemId: err: " + err.Error()) + return iot_platform, response, err + } + fmt.Println("mec033_get_om2m_pltf_by_systemId: iot_platform: " + fmt.Sprint(iot_platform)) + break + } + counter++ + if counter > 10 { + return iot_platform, nil, errors.New("Timeout waiting for OM2M platform registered") + } + time.Sleep(5 * time.Second) + } + + return iot_platform, response, nil +} + +func mec033_om2m_dev_discovery() (body []byte, response *http.Response, err error) { + fmt.Println(">>> mec033_om2m_dev_discovery") + + // Sanity checks + if sandboxName == "" { + return body, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return body, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return body, nil, errors.New("No App instance available") + } else if iotPlatformInfo.IotPlatformId == "" { + return body, nil, errors.New("No IoT platform available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/iots/v1/registered_devices?filter=(eq,requestedIotPlatformId," + iotPlatformInfo.IotPlatformId + ")" + fmt.Println("mec033_om2m_dev_discovery: url: " + url) + counter := 0 + for { // If pods are not stable and running, the request will fail and we will retry after 2 seconds + // Send request and await response + var body []byte + body, response, err = send_mec_service_request(http.MethodGet, url, nil, nil, nil, nil) + fmt.Println("mec033_om2m_dev_discovery: " + response.Status) + if err == nil && (response.StatusCode == 200 || response.StatusCode == 404) { + return body, response, nil + } + counter++ + if counter > 10 { + return body, nil, errors.New("Timeout waiting for OM2M platform registered") + } + time.Sleep(5 * time.Second) + } + + return body, nil, nil +} + +func mec033_om2m_create_device() (deviceInfo DeviceInfo, response *http.Response, err error) { + fmt.Println(">>> mec033_om2m_create_device") + + // Sanity checks + if sandboxName == "" { + return deviceInfo, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return deviceInfo, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return deviceInfo, nil, errors.New("No App instance available") + } else if iotPlatformInfo.IotPlatformId == "" { + return deviceInfo, nil, errors.New("No IoT platform available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/iots/v1/registered_devices" + fmt.Println("mec033_om2m_create_device: url: " + url) + + // Build message body + new_device := DeviceInfo{ + DeviceAuthenticationInfo: "deviceAuthenticationInfo", + DeviceMetadata: []KeyValuePair{}, + Supi: "001011234567890", + DeviceId: uuid.New().String(), + RequestedIotPlatformId: iotPlatformInfo.IotPlatformId, + } + // Add oneM2M device metadata + new_device.DeviceMetadata = []KeyValuePair{ + {Key: "ri", Value: new_device.DeviceId}, + {Key: "rn", Value: new_device.Supi + "_" + new_device.DeviceId}, + } + fmt.Println("mec033_om2m_create_device: device: " + fmt.Sprint(new_device)) + + // Send request and await response + body, err := json.Marshal(new_device) + if err != nil { + return deviceInfo, nil, err + } + body, response, err = send_mec_service_request(http.MethodPost, url, bytes.NewBuffer(body), nil, nil, nil) + fmt.Println("mec033_om2m_create_device: " + response.Status) + if err == nil && response.StatusCode == 201 { + fmt.Println("mec033_om2m_create_device: body: " + string(body)) + err = json.Unmarshal([]byte(body), &deviceInfo) + if err != nil { + return deviceInfo, response, err + } + fmt.Println("mec033_om2m_create_device: deviceInfo: " + fmt.Sprint(deviceInfo)) + return deviceInfo, response, nil + } + + return deviceInfo, response, err +} + +func mec033_om2m_delete_device(deviceId string) (response *http.Response, err error) { + fmt.Println(">>> mec033_om2m_delete_device: ", deviceId) + + // Sanity checks + if sandboxName == "" { + return response, errors.New("No sandbox available") + } else if scenarioId == -1 { + return response, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return response, errors.New("No App instance available") + } else if deviceId == "" { + return response, errors.New("No device available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/iots/v1/registered_devices/" + deviceId + fmt.Println("mec033_om2m_delete_device: url: " + url) + + // Send request and await response + _, response, err = send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil) + fmt.Println("mec033_om2m_delete_device: " + response.Status) + return response, err +} + +func mec046_om2m_sensors_discovery() (body []byte, response *http.Response, err error) { + fmt.Println(">>> mec046_om2m_sensors_discovery") + + // Sanity checks + if sandboxName == "" { + return body, nil, errors.New("No sandbox available") + } else if scenarioId == -1 { + return body, nil, errors.New("No network scenario available") + } else if appsInfo.Id == "" { + return body, nil, errors.New("No App instance available") + } + + // Set URL + url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/sens/v1//queries/sensor_discovery" + fmt.Println("mec046_om2m_sensors_discovery: url: " + url) + s := "{\n\"type\": \"CNT\",\n\"sensorPropertyList\": [\n\"saref:any\"\n]\n]\n}\n" + io_body := bytes.NewReader([]byte(s)) + counter := 0 + for { // If pods are not stable and running, the request will fail and we will retry after 2 seconds + // Send request and await response + body, response, err := send_mec_service_request(http.MethodGet, url, io_body, nil, nil, nil) + fmt.Println("mec046_om2m_sensors_discovery: " + response.Status) + if err == nil && (response.StatusCode == 200 || response.StatusCode == 404) { + return body, response, nil + } + counter++ + if counter > 10 { + return body, nil, errors.New("Timeout waiting for OM2M platform registered") + } + time.Sleep(5 * time.Second) + } + + return body, nil, nil +} + +func main() { + if len(os.Args) < 2 { + // no config argument + fmt.Errorf("Missing parameter, require file path to configurations!") + return + } + + // Read configuration file path in command line arugments + configPath := os.Args[1] + dir = strings.TrimSpace(filepath.Dir(configPath)) + fileName = strings.TrimSpace(filepath.Base(configPath)) + fmt.Println("dir: ", dir) + fmt.Println("fileName: ", fileName) + + // Retrieve environmental variable + var config Config + // trim file extension + envName := strings.TrimSuffix(fileName, ".yaml") + fmt.Println("Using config values from ", dir, "/", envName) + config, err := LoadConfig(dir, envName) + if err != nil { + fmt.Errorf(err.Error()) + return + } + // mecUrl is url of the sandbox system + mecUrl = config.SandboxUrl + // Check mecUrl if uses https + if config.HttpsOnly { + if !strings.HasPrefix(mecUrl, "https://") { + mecUrl = "https://" + mecUrl + } + } else if !config.HttpsOnly { + if !strings.HasPrefix(mecUrl, "http://") { + mecUrl = "http://" + mecUrl + } + } else { + fmt.Errorf("Wrong configuration!") + return + } + if strings.HasSuffix(mecUrl, "/") { + mecUrl = strings.TrimSuffix(mecUrl, "/") + } + mecPlateform = config.MecPlatform + fmt.Println("mecUrl: " + mecUrl) + fmt.Println("mecPlateform: " + mecPlateform) + + callbackUrl = config.CallbackUrl + ":" + config.CallbackPort + fmt.Println("callbackUrl: " + callbackUrl) + + cfg = client.NewConfiguration() + if cfg == nil { + fmt.Errorf("Failed to create client configuration!") + return + } + cfg.BasePath = mecUrl + "/sandbox-api/v1" + fmt.Println("cfg.BasePath: " + cfg.BasePath) + cl = client.NewAPIClient(cfg) + if cl == nil { + fmt.Errorf("Failed to create client reference!") + return + } + + go func() { + // Start demo9 server + reader = bufio.NewReader(os.Stdin) + + var message string = "" + for run { + // Display menu and read selection + choice := menu(message) + + // Apply it + fmt.Println("choice: ", choice) + if strings.Compare(choice[0], "q") == 0 { + run = false + break + } else { + message = process_choice(choice) + } + + } // End of 'for' statement + + }() + + // Listen for SIGKILL + go func() { + sigchan := make(chan os.Signal, 10) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + <-sigchan + fmt.Println("Waiting to shut down program !") + run = false + }() + + // Start REST API Server + go func() { + //fmt.Println(">>> Start REST API Server on port : ", config.CallbackPort) + router := NewRouter() + methods := handlers.AllowedMethods([]string{"OPTIONS", "DELETE", "GET", "HEAD", "POST", "PUT"}) + header := handlers.AllowedHeaders([]string{"content-type"}) + fmt.Println(http.ListenAndServe(":"+config.CallbackPort, handlers.CORS(methods, header)(router))) + run = false + fmt.Println("<<< Stop REST API Server") + }() + + // Listen for demo9 error exit program + go func() { + <-done + fmt.Println("Waiting to shut down program !") + run = false + }() + + for { + // Invoke graceful termination upon program kill + if !run { + fmt.Println("Invoking demo9 graceful termination") + if sandboxName != "" { + logout() + } + break + } + time.Sleep(time.Second) + } +} + +func LoadConfig(path string, name string) (config Config, err error) { + viper.SetConfigType("yaml") + viper.AddConfigPath(path) + viper.SetConfigName(name) + viper.AutomaticEnv() + + err = viper.ReadInConfig() + if err != nil { + return config, err + } + + err = viper.Unmarshal(&config) + if err != nil { + return config, err + } + return + +} + +func process_choice(choice []string) string { + + var message string + if strings.Compare(choice[0], LOGIN) == 0 { // Login + Get Namespace + Get scenarios list + Activate a scenario + // Login + userCode, verificationUri, _ = login() + if userCode == "" { + return fmt.Sprintf("Login failed") + } + message = fmt.Sprintf("User Code: %s and Verification URI is %s: ", userCode, verificationUri) + fmt.Println("message: " + message) + // Get namespace + sandbox, err := getNamespace() + if err != nil { + return err.Error() + } + message = fmt.Sprintf("Sandbox ID is: %s, wait for MEC Sandbox started and stable...", sandbox) + fmt.Println("message: " + message) + // Wait for MEC Sandbox started and stable + time.Sleep(time.Duration(10) * time.Second) + + // Retrieve the list of the scenarios + scenarios, _ = getListOfScenarios() + message = fmt.Sprintf("scenarios: %s", fmt.Sprint(scenarios)) + fmt.Println("message: " + message) + + // Activate the scenario + //scenarioId = 1 // 4g-5g-mc-v2x-fed + scenarioId = 2 // 4g-5g-mc-v2x-fed-iot + scenarioId, err = verify_idx_len(scenarioId, len(scenarios)) + if err != nil { + return fmt.Sprintf("Invalid index: %s", err.Error()) + } + err = activateScenario(scenarios[scenarioId].Id) + if err != nil { + scenarioId = -1 + scenarios = nil + return err.Error() + } + message = fmt.Sprintf("Scenario %s activated (wait some seconds before the next command), waiting for MEC pltf started and stable", scenarios[scenarioId].Id) + fmt.Println("message: " + message) + // Wait for MEC pltf started and stable + time.Sleep(time.Duration(25) * time.Second) + + appsInfo = client.ApplicationInfo{ + Id: uuid.New().String(), + Name: "demo9 test app", + NodeName: mecPlateform, + Type_: "USER", + Persist: false, + } + err = createMECAppInstId(appsInfo) + if err != nil { + scenarioId = -1 + scenarios = nil + appsInfo = client.ApplicationInfo{} + return err.Error() + } + message = fmt.Sprintf("appsInfo: %s created", fmt.Sprint(appsInfo.Id)) + fmt.Println("message: " + message) + // Wait for MEC pltf started and stable + time.Sleep(time.Duration(5) * time.Second) + + // Send mecApp confirm_ready + terminationSubscriptionID, _, err = mec011_send_confirm_ready() + if err != nil { + return err.Error() + } + message = fmt.Sprintf("Termination subscription ID: %s", terminationSubscriptionID) + fmt.Println("message: " + message) + + // // Send mecApp registration + // body, response, err := mec011_send_registration() + // if err != nil { + // scenarioId = -1 + // scenarios = nil + // appsInfo = client.ApplicationInfo{} + // return err.Error() + // } + // message = fmt.Sprintf("response body: %s", string(body)) + } else if strings.Compare(choice[0], LOGOUT) == 0 { + err := logout() + if err != nil { + return err.Error() + } + message = "Sandbox terminated" + } else if strings.Compare(choice[0], IOT_PLTF_DISCOVERY) == 0 { + iot_platforms, response, err := mec033_await_om2m_pltf_registered() + if err != nil { + return err.Error() + } + if response.StatusCode != 200 { + message = fmt.Sprintf("response status: %d", response.StatusCode) + iotPlatformInfo = IotPlatformInfo{} + } else { + message = fmt.Sprintf("response body: %s", fmt.Sprint(iot_platforms)) + iotPlatformInfo = iot_platforms[0] + } + } else if strings.Compare(choice[0], IOT_DEV_DISCOVERY_BY_ID) == 0 { + if len(choice) == 1 { + return "IoT platform ID was not set" + } + iot_platform, response, err := mec033_get_om2m_pltf_by_systemId(choice[1]) + if err != nil { + return err.Error() + } + if response.StatusCode != 200 { + message = fmt.Sprintf("response status: %d", response.StatusCode) + } else { + message = fmt.Sprintf("response body: %s", fmt.Sprint(iot_platform)) + } + } else if strings.Compare(choice[0], IOT_DEV_DISCOVERY) == 0 { + body, response, err := mec033_om2m_dev_discovery() + if err != nil { + return err.Error() + } + if response.StatusCode != 200 { + message = fmt.Sprintf("response status: %d", response.StatusCode) + } else { + message = fmt.Sprintf("response body: %s", string(body)) + } + } else if strings.Compare(choice[0], IOT_CREATE_DEVICE) == 0 { + deviceInfo, response, err := mec033_om2m_create_device() + if err != nil { + return err.Error() + } + if response.StatusCode != 200 { + message = fmt.Sprintf("response status: %d", response.StatusCode) + } else { + message = fmt.Sprintf("response body: %s", fmt.Sprint(deviceInfo)) + iotDevices = append(iotDevices, deviceInfo) + } + } else if strings.Compare(choice[0], IOT_DELETE_DEVICE) == 0 { + if len(choice) == 1 { + return "IoT device ID was not set" + } + response, err := mec033_om2m_delete_device(choice[1]) + if err != nil { + return err.Error() + } + message = fmt.Sprintf("response status: %d", response.StatusCode) + } else if strings.Compare(choice[0], STATUS) == 0 { + resp := app_status() + message = fmt.Sprintf("Current status: %s", resp) + } else { + message = fmt.Sprintf("Invalid command: %s", choice) + } + + return message +} diff --git a/examples/demo9/golang/routers.go b/examples/demo9/golang/routers.go new file mode 100644 index 0000000000000000000000000000000000000000..a2b8bb25c1d36fe53a28b7e6eac562f4639e2ccc --- /dev/null +++ b/examples/demo9/golang/routers.go @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 The AdvantEDGE Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * AdvantEDGE Application Mobility API + * + * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf)

    [Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt)

    **Micro-service**
    [meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams)

    **Type & Usage**
    Edge Service used by edge applications that want to get information about application mobility in the network

    **Note**
    AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). + * + * API version: 2.2.1 + * Contact: AdvantEDGE@InterDigital.com + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package main + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" +) + +type HttpRoute struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +type HttpRoutes []HttpRoute + +func NewRouter() *mux.Router { + var handler http.Handler + router := mux.NewRouter().StrictSlash(true) + for _, route := range routes { + handler = route.HandlerFunc + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + } + + return router +} + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello World!") +} + +var routes = HttpRoutes{ + HttpRoute{ + "Index", + "GET", + "/", + Index, + }, +} diff --git a/examples/demo9/golang/run.sh b/examples/demo9/golang/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..de4868d870ff1269bd46dac4ef07d0657d4c844b --- /dev/null +++ b/examples/demo9/golang/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e +set +x + +docker run -it --rm --expose 80/tcp meep-docker-registry:30001/demo9 + +echo "" +echo ">>> Done" diff --git a/examples/demo9/python/README.md b/examples/demo9/python/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5ac574688a4d42772e065a08af7ab5c4af800d79 --- /dev/null +++ b/examples/demo9/python/README.md @@ -0,0 +1,6 @@ + +docker pull quay.io/jupyter/base-notebook:latest + +cd ~/etsi-mec-sandbox/examples/demo9/python/ && docker run --rm -it -d --expose 31111 -p 31111:31111 -p 9999:8888 -v"$PWD:/home/jovyan/work" quay.io/jupyter/base-notebook:latest + +curl --verbose --request GET http://mec-platform2.etsi.org:31111/sandbox/v1/statistic/v1/quantity --header "Accept: application/json" --data '{"time":20180124,"data1":"[1516752000,11590.6,11616.9,11590.4,11616.9,0.25202387,1516752060,11622.4,11651.7,11622.4,11644.6,1.03977764]"}' diff --git a/examples/demo9/python/mecapp/.gitignore b/examples/demo9/python/mecapp/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a655050c2631466828b5b8bfc59ae27f9ac02dc5 --- /dev/null +++ b/examples/demo9/python/mecapp/.gitignore @@ -0,0 +1,64 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/examples/demo9/python/mecapp/.swagger-codegen-ignore b/examples/demo9/python/mecapp/.swagger-codegen-ignore new file mode 100644 index 0000000000000000000000000000000000000000..c5fa491b4c557bf997d5dd21797de782545dc9e5 --- /dev/null +++ b/examples/demo9/python/mecapp/.swagger-codegen-ignore @@ -0,0 +1,23 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/examples/demo9/python/mecapp/.swagger-codegen/VERSION b/examples/demo9/python/mecapp/.swagger-codegen/VERSION new file mode 100644 index 0000000000000000000000000000000000000000..2e29f51f4bb7da425a3475d214e75fcad2161e08 --- /dev/null +++ b/examples/demo9/python/mecapp/.swagger-codegen/VERSION @@ -0,0 +1 @@ +3.0.55 \ No newline at end of file diff --git a/examples/demo9/python/mecapp/.travis.yml b/examples/demo9/python/mecapp/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..dd6c4450aa902ae68479c3d76d45145e18d6052e --- /dev/null +++ b/examples/demo9/python/mecapp/.travis.yml @@ -0,0 +1,13 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: + - "3.2" + - "3.3" + - "3.4" + - "3.5" + #- "3.5-dev" # 3.5 development branch + #- "nightly" # points to the latest development branch e.g. 3.6-dev +# command to install dependencies +install: "pip install -r requirements.txt" +# command to run tests +script: nosetests diff --git a/examples/demo9/python/mecapp/README.md b/examples/demo9/python/mecapp/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8ab9f19f4ed6bc5a84a9243aed0f145e598fcc6a --- /dev/null +++ b/examples/demo9/python/mecapp/README.md @@ -0,0 +1,143 @@ +# swagger-client +The MEC Sandbox API described using OpenAPI + +This Python package is automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) project: + +- API version: 0.0.7 +- Package version: 1.0.0 +- Build package: io.swagger.codegen.v3.generators.python.PythonClientCodegen + +## Requirements. + +Python 2.7 and 3.4+ + +## Installation & Usage +### pip install + +If the python package is hosted on Github, you can install directly from Github + +```sh +pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git +``` +(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git`) + +Then import the package: +```python +import swagger_client +``` + +### Setuptools + +Install via [Setuptools](http://pypi.python.org/pypi/setuptools). + +```sh +python setup.py install --user +``` +(or `sudo python setup.py install` to install the package for all users) + +Then import the package: +```python +import swagger_client +``` + +## Getting Started + +Please follow the [installation procedure](#installation--usage) and then run the following: + +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.AuthorizationApi(swagger_client.ApiClient(configuration)) +provider = 'provider_example' # str | Oauth provider + +try: + # Initiate OAuth login procedure and creates a MEC Sandbox instance + api_response = api_instance.login(provider) + pprint(api_response) +except ApiException as e: + print("Exception when calling AuthorizationApi->login: %s\n" % e) + +# create an instance of the API class +api_instance = swagger_client.AuthorizationApi(swagger_client.ApiClient(configuration)) +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Terminates User Session and delete the Sandbox instance + api_instance.logout(sandbox_name) +except ApiException as e: + print("Exception when calling AuthorizationApi->logout: %s\n" % e) +``` + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*AuthorizationApi* | [**login**](docs/AuthorizationApi.md#login) | **POST** /login | Initiate OAuth login procedure and creates a MEC Sandbox instance +*AuthorizationApi* | [**logout**](docs/AuthorizationApi.md#logout) | **POST** /logout | Terminates User Session and delete the Sandbox instance +*SandboxAppInstancesApi* | [**sandbox_app_instances_delete**](docs/SandboxAppInstancesApi.md#sandbox_app_instances_delete) | **DELETE** /sandboxAppInstances/{sandbox_name}/{app_instance_id} | Delete an existing application instance +*SandboxAppInstancesApi* | [**sandbox_app_instances_get**](docs/SandboxAppInstancesApi.md#sandbox_app_instances_get) | **GET** /sandboxAppInstances/{sandbox_name} | Get the list of the available application instance identifiers +*SandboxAppInstancesApi* | [**sandbox_app_instances_post**](docs/SandboxAppInstancesApi.md#sandbox_app_instances_post) | **POST** /sandboxAppInstances/{sandbox_name} | Create a new application instance identifier +*SandboxLogsSubscriptionsApi* | [**sandbox_logs_subscriptions_delete**](docs/SandboxLogsSubscriptionsApi.md#sandbox_logs_subscriptions_delete) | **DELETE** /sandboxLogsSubscriptions/{sandbox_name}/{subscription_reference} | Subscription to receive logs from the sandbox +*SandboxLogsSubscriptionsApi* | [**sandbox_logs_subscriptions_post**](docs/SandboxLogsSubscriptionsApi.md#sandbox_logs_subscriptions_post) | **POST** /sandboxLogsSubscriptions/{sandbox_name} | Subscription to receive logs from the sandbox +*SandboxMECServicesApi* | [**sandbox_mec_services_get**](docs/SandboxMECServicesApi.md#sandbox_mec_services_get) | **GET** /sandboxMecServices/{sandbox_name} | Get the list of the available MEC services +*SandboxNetworkScenariosApi* | [**sandbox_individual_network_scenarios_get**](docs/SandboxNetworkScenariosApi.md#sandbox_individual_network_scenarios_get) | **GET** /sandboxNetworkScenarios/{sandbox_name} | Get description of a Network Scenario to be used. +*SandboxNetworkScenariosApi* | [**sandbox_network_scenario_delete**](docs/SandboxNetworkScenariosApi.md#sandbox_network_scenario_delete) | **DELETE** /sandboxNetworkScenarios/{sandbox_name}/{network_scenario_id} | Deactivate the Network Scenario. +*SandboxNetworkScenariosApi* | [**sandbox_network_scenario_post**](docs/SandboxNetworkScenariosApi.md#sandbox_network_scenario_post) | **POST** /sandboxNetworkScenarios/{sandbox_name} | Selects the Network Scenario to be activated. +*SandboxNetworkScenariosApi* | [**sandbox_network_scenarios_get**](docs/SandboxNetworkScenariosApi.md#sandbox_network_scenarios_get) | **GET** /sandboxNetworkScenarios | Get the list of the available network scenarios +*SandboxUEControllerApi* | [**sandbox_ue_controller_get**](docs/SandboxUEControllerApi.md#sandbox_ue_controller_get) | **GET** /sandboxUeController/{sandbox_name} | Get the list of the available UEs (e.g. \"Stationary UE\") +*SandboxUEControllerApi* | [**sandbox_ue_controller_patch**](docs/SandboxUEControllerApi.md#sandbox_ue_controller_patch) | **PATCH** /sandboxUeController/{sandbox_name} | set the new value of the UE + +## Documentation For Models + + - [ApplicationInfo](docs/ApplicationInfo.md) + - [CellularDomainConfig](docs/CellularDomainConfig.md) + - [CellularPoaConfig](docs/CellularPoaConfig.md) + - [ConnectivityConfig](docs/ConnectivityConfig.md) + - [CpuConfig](docs/CpuConfig.md) + - [D2dConfig](docs/D2dConfig.md) + - [DNConfig](docs/DNConfig.md) + - [Deployment](docs/Deployment.md) + - [Domain](docs/Domain.md) + - [EgressService](docs/EgressService.md) + - [ExternalConfig](docs/ExternalConfig.md) + - [GeoData](docs/GeoData.md) + - [GpuConfig](docs/GpuConfig.md) + - [IngressService](docs/IngressService.md) + - [LineString](docs/LineString.md) + - [MemoryConfig](docs/MemoryConfig.md) + - [NetworkCharacteristics](docs/NetworkCharacteristics.md) + - [NetworkLocation](docs/NetworkLocation.md) + - [PhysicalLocation](docs/PhysicalLocation.md) + - [Poa4GConfig](docs/Poa4GConfig.md) + - [Poa5GConfig](docs/Poa5GConfig.md) + - [PoaWifiConfig](docs/PoaWifiConfig.md) + - [Point](docs/Point.md) + - [ProblemDetails](docs/ProblemDetails.md) + - [Process](docs/Process.md) + - [Sandbox](docs/Sandbox.md) + - [SandboxAppInstances](docs/SandboxAppInstances.md) + - [SandboxLogsSubscriptions](docs/SandboxLogsSubscriptions.md) + - [SandboxMecServices](docs/SandboxMecServices.md) + - [SandboxNetworkScenario](docs/SandboxNetworkScenario.md) + - [Scenario](docs/Scenario.md) + - [ScenarioConfig](docs/ScenarioConfig.md) + - [ServiceConfig](docs/ServiceConfig.md) + - [ServicePort](docs/ServicePort.md) + - [UE](docs/UE.md) + - [Zone](docs/Zone.md) + +## Documentation For Authorization + + All endpoints do not require authorization. + + +## Author + +cti_support@etsi.org diff --git a/examples/demo9/python/mecapp/docs/ApplicationInfo.md b/examples/demo9/python/mecapp/docs/ApplicationInfo.md new file mode 100644 index 0000000000000000000000000000000000000000..cea763beb2365e101ae8b7f60cee283f6678f0e9 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ApplicationInfo.md @@ -0,0 +1,13 @@ +# ApplicationInfo + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | Application Instance UUID | [optional] +**name** | **str** | Application name | +**node_name** | **str** | Name of node where application instance is running | +**type** | **str** | Application Type | [optional] +**persist** | **bool** | Reserved for internal platform usage | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/AuthorizationApi.md b/examples/demo9/python/mecapp/docs/AuthorizationApi.md new file mode 100644 index 0000000000000000000000000000000000000000..11d4a7e233aa83a81988193a8f61def851066568 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/AuthorizationApi.md @@ -0,0 +1,104 @@ +# swagger_client.AuthorizationApi + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**login**](AuthorizationApi.md#login) | **POST** /login | Initiate OAuth login procedure and creates a MEC Sandbox instance +[**logout**](AuthorizationApi.md#logout) | **POST** /logout | Terminates User Session and delete the Sandbox instance + +# **login** +> Sandbox login(provider) + +Initiate OAuth login procedure and creates a MEC Sandbox instance + +Initiate OAuth login procedure and creates a MEC Sandbox instance + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.AuthorizationApi() +provider = 'provider_example' # str | Oauth provider + +try: + # Initiate OAuth login procedure and creates a MEC Sandbox instance + api_response = api_instance.login(provider) + pprint(api_response) +except ApiException as e: + print("Exception when calling AuthorizationApi->login: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **provider** | **str**| Oauth provider | + +### Return type + +[**Sandbox**](Sandbox.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **logout** +> logout(sandbox_name) + +Terminates User Session and delete the Sandbox instance + +Terminates User Session and delete the Sandbox instance + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.AuthorizationApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Terminates User Session and delete the Sandbox instance + api_instance.logout(sandbox_name) +except ApiException as e: + print("Exception when calling AuthorizationApi->logout: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/CellularDomainConfig.md b/examples/demo9/python/mecapp/docs/CellularDomainConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..c16f44d635ee7f81fcc01b734b1fd6eaa7b6b989 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/CellularDomainConfig.md @@ -0,0 +1,11 @@ +# CellularDomainConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**mnc** | **str** | Mobile Network Code part of PLMN identity as defined in ETSI TS 136 413 | [optional] +**mcc** | **str** | Mobile Country Code part of PLMN identity as defined in ETSI TS 136 413 | [optional] +**default_cell_id** | **str** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 if no cellId is defined for the cell or if not applicable | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/CellularPoaConfig.md b/examples/demo9/python/mecapp/docs/CellularPoaConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..58fa271693fcf0e33b999dba7809ac3edbb845f2 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/CellularPoaConfig.md @@ -0,0 +1,9 @@ +# CellularPoaConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**cell_id** | **str** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/ConnectivityConfig.md b/examples/demo9/python/mecapp/docs/ConnectivityConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..e031e425dab020fb8807184ac5c8d4e4db0c6bc4 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ConnectivityConfig.md @@ -0,0 +1,9 @@ +# ConnectivityConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**model** | **str** | Connectivity Model: <li>OPEN: Any node in the scenario can communicate with any node <li>PDU: Terminal nodes (UE) require a PDU session to the target DN | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/CpuConfig.md b/examples/demo9/python/mecapp/docs/CpuConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..ea62fa32f810900e6c34e63edca8ebc8807ce613 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/CpuConfig.md @@ -0,0 +1,10 @@ +# CpuConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**min** | **float** | Minimum requested CPU | [optional] +**max** | **float** | Maximum requested CPU | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/D2dConfig.md b/examples/demo9/python/mecapp/docs/D2dConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..0cc7ba9e53a4f4f571de58c86a8754e392e00ca4 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/D2dConfig.md @@ -0,0 +1,10 @@ +# D2dConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**d2d_max_distance** | **float** | Maximum distance for D2D. Default distance is 100m | [optional] +**disable_d2d_via_network** | **bool** | Enable-Disable D2D via network. Default value is false | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/DNConfig.md b/examples/demo9/python/mecapp/docs/DNConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..98ad28a1eeab591c390700d60059e93b89fabe7b --- /dev/null +++ b/examples/demo9/python/mecapp/docs/DNConfig.md @@ -0,0 +1,11 @@ +# DNConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**dnn** | **str** | Data Network Name | [optional] +**ladn** | **bool** | true: Data network serves local area only false: Data network is not limited to local area | [optional] +**ecsp** | **str** | Edge Compute Service Provider | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Deployment.md b/examples/demo9/python/mecapp/docs/Deployment.md new file mode 100644 index 0000000000000000000000000000000000000000..126d3ec39b09130d36512e778a61c41406f065ab --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Deployment.md @@ -0,0 +1,18 @@ +# Deployment + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**net_char** | [**NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] +**connectivity** | [**ConnectivityConfig**](ConnectivityConfig.md) | | [optional] +**d2d** | [**D2dConfig**](D2dConfig.md) | | [optional] +**inter_domain_latency** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] +**inter_domain_latency_variation** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] +**inter_domain_throughput** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] +**inter_domain_packet_loss** | **float** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] +**meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**user_meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**domains** | [**list[Domain]**](Domain.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Domain.md b/examples/demo9/python/mecapp/docs/Domain.md new file mode 100644 index 0000000000000000000000000000000000000000..3eb7486973a5cdd4b122872dbcd0a7b591e4231a --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Domain.md @@ -0,0 +1,20 @@ +# Domain + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | Unique domain ID | [optional] +**name** | **str** | Domain name | [optional] +**type** | **str** | Domain type | [optional] +**net_char** | [**NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] +**inter_zone_latency** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] +**inter_zone_latency_variation** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] +**inter_zone_throughput** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] +**inter_zone_packet_loss** | **float** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] +**meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**user_meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**cellular_domain_config** | [**CellularDomainConfig**](CellularDomainConfig.md) | | [optional] +**zones** | [**list[Zone]**](Zone.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/EgressService.md b/examples/demo9/python/mecapp/docs/EgressService.md new file mode 100644 index 0000000000000000000000000000000000000000..3bc6057c0391a8f1b7aa5f49413ca6370f4f7a07 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/EgressService.md @@ -0,0 +1,13 @@ +# EgressService + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | Service name | [optional] +**me_svc_name** | **str** | Multi-Edge service name, if any | [optional] +**ip** | **str** | External node IP address | [optional] +**port** | **int** | Service port number | [optional] +**protocol** | **str** | Service protocol (TCP or UDP) | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/ExternalConfig.md b/examples/demo9/python/mecapp/docs/ExternalConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..3ca868f0e7dc796130399a21051ebf99880dc6a2 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ExternalConfig.md @@ -0,0 +1,10 @@ +# ExternalConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**ingress_service_map** | [**list[IngressService]**](IngressService.md) | | [optional] +**egress_service_map** | [**list[EgressService]**](EgressService.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/GeoData.md b/examples/demo9/python/mecapp/docs/GeoData.md new file mode 100644 index 0000000000000000000000000000000000000000..63958903fe1592a103d67fcac58ba36d1b2e8130 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/GeoData.md @@ -0,0 +1,15 @@ +# GeoData + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**location** | [**Point**](Point.md) | | [optional] +**radius** | **float** | Optional - Radius (in meters) around the location | [optional] +**path** | [**LineString**](LineString.md) | | [optional] +**eop_mode** | **str** | End-of-Path mode: <li>LOOP: When path endpoint is reached, start over from the beginning <li>REVERSE: When path endpoint is reached, return on the reverse path | [optional] +**velocity** | **float** | Speed of movement along path in m/s | [optional] +**d2d_in_range** | **list[str]** | | [optional] +**poa_in_range** | **list[str]** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/GpuConfig.md b/examples/demo9/python/mecapp/docs/GpuConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..acd3e6265f68cd1261dd3547670ff50c870c4aea --- /dev/null +++ b/examples/demo9/python/mecapp/docs/GpuConfig.md @@ -0,0 +1,10 @@ +# GpuConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**type** | **str** | Requested GPU type | [optional] +**count** | **int** | Number of GPUs requested | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/IngressService.md b/examples/demo9/python/mecapp/docs/IngressService.md new file mode 100644 index 0000000000000000000000000000000000000000..195a468294231fe1e04fe3cecf97e7c7468afd78 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/IngressService.md @@ -0,0 +1,12 @@ +# IngressService + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | Service name (unique or multi-edge) | [optional] +**port** | **int** | Internal service port number | [optional] +**external_port** | **int** | Externally-exposed unique service port in range (30000 - 32767) | [optional] +**protocol** | **str** | Service protocol (TCP or UDP) | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/LineString.md b/examples/demo9/python/mecapp/docs/LineString.md new file mode 100644 index 0000000000000000000000000000000000000000..493827c014603b1e292e5f79a4df9780a5cd4fd9 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/LineString.md @@ -0,0 +1,10 @@ +# LineString + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**type** | **str** | Must be LineString | +**coordinates** | **list[list[float]]** | For a LineString, coordinates is an array of two or more positions; a position is an array of two decimal numbers (longitude and latitude precisely in that order) | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/MemoryConfig.md b/examples/demo9/python/mecapp/docs/MemoryConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..96af92cd25d56c0d8bedeb4c76823e9a39dda05a --- /dev/null +++ b/examples/demo9/python/mecapp/docs/MemoryConfig.md @@ -0,0 +1,10 @@ +# MemoryConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**min** | **int** | Minimum requested memory | [optional] +**max** | **int** | Maximum requested memory | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/NetworkCharacteristics.md b/examples/demo9/python/mecapp/docs/NetworkCharacteristics.md new file mode 100644 index 0000000000000000000000000000000000000000..3ee98d790f88ab66d5e2f1e2e5cae072e1664f5a --- /dev/null +++ b/examples/demo9/python/mecapp/docs/NetworkCharacteristics.md @@ -0,0 +1,15 @@ +# NetworkCharacteristics + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**latency** | **int** | Latency in ms | [optional] +**latency_variation** | **int** | Latency variation in ms | [optional] +**latency_distribution** | **str** | Latency distribution. Can only be set in the Scenario Deployment network characteristics, ignored otherwise. Latency distribution is set for the whole network and applied to every end-to-end traffic flows. Default value is 'Normal' distribution. | [optional] +**throughput** | **int** | **DEPRECATED** As of release 1.5.0, replaced by throughputUl and throughputDl | [optional] +**throughput_dl** | **int** | Downlink throughput limit in Mbps | [optional] +**throughput_ul** | **int** | Uplink throughput limit in Mbps | [optional] +**packet_loss** | **float** | Packet loss percentage | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/NetworkLocation.md b/examples/demo9/python/mecapp/docs/NetworkLocation.md new file mode 100644 index 0000000000000000000000000000000000000000..8cf3f2192c09bfc35a51d324c4e1819838abaed8 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/NetworkLocation.md @@ -0,0 +1,24 @@ +# NetworkLocation + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | Unique network location ID | [optional] +**name** | **str** | Network location name | [optional] +**type** | **str** | Network location type | [optional] +**net_char** | [**NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] +**terminal_link_latency** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] +**terminal_link_latency_variation** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] +**terminal_link_throughput** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] +**terminal_link_packet_loss** | **float** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] +**meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**user_meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**cellular_poa_config** | [**CellularPoaConfig**](CellularPoaConfig.md) | | [optional] +**poa4_g_config** | [**Poa4GConfig**](Poa4GConfig.md) | | [optional] +**poa5_g_config** | [**Poa5GConfig**](Poa5GConfig.md) | | [optional] +**poa_wifi_config** | [**PoaWifiConfig**](PoaWifiConfig.md) | | [optional] +**geo_data** | [**GeoData**](GeoData.md) | | [optional] +**physical_locations** | [**list[PhysicalLocation]**](PhysicalLocation.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/PhysicalLocation.md b/examples/demo9/python/mecapp/docs/PhysicalLocation.md new file mode 100644 index 0000000000000000000000000000000000000000..96f0f7b4f5d5caae3f2f292d9c8535e455b5e9f2 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/PhysicalLocation.md @@ -0,0 +1,27 @@ +# PhysicalLocation + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | Unique physical location ID | [optional] +**name** | **str** | Physical location name | [optional] +**type** | **str** | Physical location type | [optional] +**is_external** | **bool** | true: Physical location is external to MEEP false: Physical location is internal to MEEP | [optional] +**geo_data** | [**GeoData**](GeoData.md) | | [optional] +**network_locations_in_range** | **list[str]** | | [optional] +**connected** | **bool** | true: Physical location has network connectivity false: Physical location has no network connectivity | [optional] +**wireless** | **bool** | true: Physical location uses a wireless connection false: Physical location uses a wired connection | [optional] +**wireless_type** | **str** | Prioritized, comma-separated list of supported wireless connection types. Default priority if not specififed is 'wifi,5g,4g,other'. Wireless connection types: - 4g - 5g - wifi - other | [optional] +**data_network** | [**DNConfig**](DNConfig.md) | | [optional] +**meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**user_meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**processes** | [**list[Process]**](Process.md) | | [optional] +**net_char** | [**NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] +**link_latency** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] +**link_latency_variation** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] +**link_throughput** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] +**link_packet_loss** | **float** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] +**mac_id** | **str** | Physical location MAC Address | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Poa4GConfig.md b/examples/demo9/python/mecapp/docs/Poa4GConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..4188e23c1ad00d9c13143a909bd10cb727af1da3 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Poa4GConfig.md @@ -0,0 +1,9 @@ +# Poa4GConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**cell_id** | **str** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Poa5GConfig.md b/examples/demo9/python/mecapp/docs/Poa5GConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..3efdfddbd981b1485dc08cd99a1273115be139e6 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Poa5GConfig.md @@ -0,0 +1,9 @@ +# Poa5GConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**cell_id** | **str** | The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the NR serving the cell | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/PoaWifiConfig.md b/examples/demo9/python/mecapp/docs/PoaWifiConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..31b4fa8e86a48626689b2f027d9b24f7d29580c0 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/PoaWifiConfig.md @@ -0,0 +1,9 @@ +# PoaWifiConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**mac_id** | **str** | WIFI POA MAC Address | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Point.md b/examples/demo9/python/mecapp/docs/Point.md new file mode 100644 index 0000000000000000000000000000000000000000..6dfae907db493b871b69e2727dd7f540c1db66a8 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Point.md @@ -0,0 +1,10 @@ +# Point + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**type** | **str** | Must be Point | +**coordinates** | **list[float]** | For a Point, coordinates MUST be an array of two decimal numbers; longitude and latitude precisely in that order | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/ProblemDetails.md b/examples/demo9/python/mecapp/docs/ProblemDetails.md new file mode 100644 index 0000000000000000000000000000000000000000..d454ef453a5f9996b6e44ee6166e1f6d525532a2 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ProblemDetails.md @@ -0,0 +1,13 @@ +# ProblemDetails + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**type** | **str** | A URI reference according to IETF RFC 3986 that identifies the problem type. It is encouraged that the URI provides human-readable documentation for the problem (e.g. using HTML) when dereferenced. When this member is not present, its value is assumed to be \"about:blank\". | [optional] +**title** | **str** | A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization. If type is given and other than \"about:blank\", this attribute shall also be provided. A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4). | [optional] +**status** | **int** | The HTTP status code for this occurrence of the problem. The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. | +**detail** | **str** | A human-readable explanation specific to this occurrence of the problem. | +**instance** | **str** | A URI reference that identifies the specific occurrence of the problem. It may yield further information if dereferenced. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Process.md b/examples/demo9/python/mecapp/docs/Process.md new file mode 100644 index 0000000000000000000000000000000000000000..30dc93224164c3ffdccbf7e3bede59c9e8bafb67 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Process.md @@ -0,0 +1,33 @@ +# Process + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | Unique process ID | [optional] +**name** | **str** | Process name | [optional] +**type** | **str** | Process type | [optional] +**is_external** | **bool** | true: process is external to MEEP false: process is internal to MEEP | [optional] +**image** | **str** | Docker image to deploy inside MEEP | [optional] +**environment** | **str** | Environment variables using the format NAME=\"value\",NAME=\"value\",NAME=\"value\" | [optional] +**command_arguments** | **str** | Arguments to command executable | [optional] +**command_exe** | **str** | Executable to invoke at container start up | [optional] +**service_config** | [**ServiceConfig**](ServiceConfig.md) | | [optional] +**gpu_config** | [**GpuConfig**](GpuConfig.md) | | [optional] +**memory_config** | [**MemoryConfig**](MemoryConfig.md) | | [optional] +**cpu_config** | [**CpuConfig**](CpuConfig.md) | | [optional] +**external_config** | [**ExternalConfig**](ExternalConfig.md) | | [optional] +**status** | **str** | Process status | [optional] +**user_chart_location** | **str** | Chart location for the deployment of the chart provided by the user | [optional] +**user_chart_alternate_values** | **str** | Chart values.yaml file location for the deployment of the chart provided by the user | [optional] +**user_chart_group** | **str** | Chart supplemental information related to the group (service) | [optional] +**meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**user_meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**net_char** | [**NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] +**app_latency** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latency | [optional] +**app_latency_variation** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation | [optional] +**app_throughput** | **int** | **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl | [optional] +**app_packet_loss** | **float** | **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss | [optional] +**placement_id** | **str** | Identifier used for process placement in AdvantEDGE cluster | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Sandbox.md b/examples/demo9/python/mecapp/docs/Sandbox.md new file mode 100644 index 0000000000000000000000000000000000000000..78f643a7c3f6fc9c237788f0d9cf39072ffb0e3c --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Sandbox.md @@ -0,0 +1,9 @@ +# Sandbox + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | Sandbox name | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxAppInstances.md b/examples/demo9/python/mecapp/docs/SandboxAppInstances.md new file mode 100644 index 0000000000000000000000000000000000000000..ff2dd7e66480d0ee57fe1f50a73e3b47909f902a --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxAppInstances.md @@ -0,0 +1,9 @@ +# SandboxAppInstances + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | The application instance identifier. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxAppInstancesApi.md b/examples/demo9/python/mecapp/docs/SandboxAppInstancesApi.md new file mode 100644 index 0000000000000000000000000000000000000000..408c3ecd233b897122b863b59e2a6dcbff1b5681 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxAppInstancesApi.md @@ -0,0 +1,157 @@ +# swagger_client.SandboxAppInstancesApi + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**sandbox_app_instances_delete**](SandboxAppInstancesApi.md#sandbox_app_instances_delete) | **DELETE** /sandboxAppInstances/{sandbox_name}/{app_instance_id} | Delete an existing application instance +[**sandbox_app_instances_get**](SandboxAppInstancesApi.md#sandbox_app_instances_get) | **GET** /sandboxAppInstances/{sandbox_name} | Get the list of the available application instance identifiers +[**sandbox_app_instances_post**](SandboxAppInstancesApi.md#sandbox_app_instances_post) | **POST** /sandboxAppInstances/{sandbox_name} | Create a new application instance identifier + +# **sandbox_app_instances_delete** +> sandbox_app_instances_delete(sandbox_name, app_instance_id) + +Delete an existing application instance + +This method removes an existing application instance + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxAppInstancesApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier +app_instance_id = 'app_instance_id_example' # str | It uniquely identifies a MEC application instance identifier + +try: + # Delete an existing application instance + api_instance.sandbox_app_instances_delete(sandbox_name, app_instance_id) +except ApiException as e: + print("Exception when calling SandboxAppInstancesApi->sandbox_app_instances_delete: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + **app_instance_id** | **str**| It uniquely identifies a MEC application instance identifier | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_app_instances_get** +> list[ApplicationInfo] sandbox_app_instances_get(sandbox_name) + +Get the list of the available application instance identifiers + +This method retrieves the list of the available application instance identifiers + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxAppInstancesApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Get the list of the available application instance identifiers + api_response = api_instance.sandbox_app_instances_get(sandbox_name) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxAppInstancesApi->sandbox_app_instances_get: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +[**list[ApplicationInfo]**](ApplicationInfo.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_app_instances_post** +> list[ApplicationInfo] sandbox_app_instances_post(body, sandbox_name) + +Create a new application instance identifier + +This method creates a new application instance + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxAppInstancesApi() +body = swagger_client.ApplicationInfo() # ApplicationInfo | Pet to add to the store +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Create a new application instance identifier + api_response = api_instance.sandbox_app_instances_post(body, sandbox_name) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxAppInstancesApi->sandbox_app_instances_post: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **body** | [**ApplicationInfo**](ApplicationInfo.md)| Pet to add to the store | + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +[**list[ApplicationInfo]**](ApplicationInfo.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxLogsSubscriptions.md b/examples/demo9/python/mecapp/docs/SandboxLogsSubscriptions.md new file mode 100644 index 0000000000000000000000000000000000000000..5ac3dc4bfbef433a87d35c02eddd606a227256b1 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxLogsSubscriptions.md @@ -0,0 +1,10 @@ +# SandboxLogsSubscriptions + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**callback_reference** | **str** | The callback to notify log messages. | +**subscription_reference** | **str** | The reference of the subscription. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxLogsSubscriptionsApi.md b/examples/demo9/python/mecapp/docs/SandboxLogsSubscriptionsApi.md new file mode 100644 index 0000000000000000000000000000000000000000..b19495e52465f37dc06cc73d8ec4a08103765e0c --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxLogsSubscriptionsApi.md @@ -0,0 +1,106 @@ +# swagger_client.SandboxLogsSubscriptionsApi + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**sandbox_logs_subscriptions_delete**](SandboxLogsSubscriptionsApi.md#sandbox_logs_subscriptions_delete) | **DELETE** /sandboxLogsSubscriptions/{sandbox_name}/{subscription_reference} | Subscription to receive logs from the sandbox +[**sandbox_logs_subscriptions_post**](SandboxLogsSubscriptionsApi.md#sandbox_logs_subscriptions_post) | **POST** /sandboxLogsSubscriptions/{sandbox_name} | Subscription to receive logs from the sandbox + +# **sandbox_logs_subscriptions_delete** +> sandbox_logs_subscriptions_delete(sandbox_name, subscription_reference) + +Subscription to receive logs from the sandbox + +This method is used to receive logs from the sandbox. + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxLogsSubscriptionsApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier +subscription_reference = 'subscription_reference_example' # str | It uniquely identifies subscription reference to receive logs from the sandbox + +try: + # Subscription to receive logs from the sandbox + api_instance.sandbox_logs_subscriptions_delete(sandbox_name, subscription_reference) +except ApiException as e: + print("Exception when calling SandboxLogsSubscriptionsApi->sandbox_logs_subscriptions_delete: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + **subscription_reference** | **str**| It uniquely identifies subscription reference to receive logs from the sandbox | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_logs_subscriptions_post** +> list[SandboxLogsSubscriptions] sandbox_logs_subscriptions_post(sandbox_name) + +Subscription to receive logs from the sandbox + +This method is used to receive logs from the sandbox. + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxLogsSubscriptionsApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Subscription to receive logs from the sandbox + api_response = api_instance.sandbox_logs_subscriptions_post(sandbox_name) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxLogsSubscriptionsApi->sandbox_logs_subscriptions_post: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +[**list[SandboxLogsSubscriptions]**](SandboxLogsSubscriptions.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxMECServicesApi.md b/examples/demo9/python/mecapp/docs/SandboxMECServicesApi.md new file mode 100644 index 0000000000000000000000000000000000000000..2376525375af21397d61d1908c641d04457e88b6 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxMECServicesApi.md @@ -0,0 +1,56 @@ +# swagger_client.SandboxMECServicesApi + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**sandbox_mec_services_get**](SandboxMECServicesApi.md#sandbox_mec_services_get) | **GET** /sandboxMecServices/{sandbox_name} | Get the list of the available MEC services + +# **sandbox_mec_services_get** +> list[SandboxMecServices] sandbox_mec_services_get(sandbox_name) + +Get the list of the available MEC services + +This method retrieves the list of the available MEC services. + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxMECServicesApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Get the list of the available MEC services + api_response = api_instance.sandbox_mec_services_get(sandbox_name) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxMECServicesApi->sandbox_mec_services_get: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +[**list[SandboxMecServices]**](SandboxMecServices.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxMecServices.md b/examples/demo9/python/mecapp/docs/SandboxMecServices.md new file mode 100644 index 0000000000000000000000000000000000000000..f9799f154c871933037f162b8b9175e1027bf8dd --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxMecServices.md @@ -0,0 +1,10 @@ +# SandboxMecServices + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | The MEC service name. | +**service_id** | **str** | When a MEC service is selected, this field contains a token which shall be used in MEC service API URI. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxNetworkScenario.md b/examples/demo9/python/mecapp/docs/SandboxNetworkScenario.md new file mode 100644 index 0000000000000000000000000000000000000000..cd395dd1483d6b247f7f7b3f5a41280fbfc4df83 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxNetworkScenario.md @@ -0,0 +1,9 @@ +# SandboxNetworkScenario + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | The network scenario name. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxNetworkScenariosApi.md b/examples/demo9/python/mecapp/docs/SandboxNetworkScenariosApi.md new file mode 100644 index 0000000000000000000000000000000000000000..9199d8a78f9388ebb12144897752fe19d204bfee --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxNetworkScenariosApi.md @@ -0,0 +1,207 @@ +# swagger_client.SandboxNetworkScenariosApi + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**sandbox_individual_network_scenarios_get**](SandboxNetworkScenariosApi.md#sandbox_individual_network_scenarios_get) | **GET** /sandboxNetworkScenarios/{sandbox_name} | Get description of a Network Scenario to be used. +[**sandbox_network_scenario_delete**](SandboxNetworkScenariosApi.md#sandbox_network_scenario_delete) | **DELETE** /sandboxNetworkScenarios/{sandbox_name}/{network_scenario_id} | Deactivate the Network Scenario. +[**sandbox_network_scenario_post**](SandboxNetworkScenariosApi.md#sandbox_network_scenario_post) | **POST** /sandboxNetworkScenarios/{sandbox_name} | Selects the Network Scenario to be activated. +[**sandbox_network_scenarios_get**](SandboxNetworkScenariosApi.md#sandbox_network_scenarios_get) | **GET** /sandboxNetworkScenarios | Get the list of the available network scenarios + +# **sandbox_individual_network_scenarios_get** +> list[Scenario] sandbox_individual_network_scenarios_get(sandbox_name, network_scenario_id) + +Get description of a Network Scenario to be used. + +This method retrive description of a the network scenario + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxNetworkScenariosApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier +network_scenario_id = 'network_scenario_id_example' # str | Network scenario to retrieve + +try: + # Get description of a Network Scenario to be used. + api_response = api_instance.sandbox_individual_network_scenarios_get(sandbox_name, network_scenario_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxNetworkScenariosApi->sandbox_individual_network_scenarios_get: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + **network_scenario_id** | **str**| Network scenario to retrieve | + +### Return type + +[**list[Scenario]**](Scenario.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_network_scenario_delete** +> sandbox_network_scenario_delete(sandbox_name, network_scenario_id) + +Deactivate the Network Scenario. + +This method deactivates the network scenario + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxNetworkScenariosApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier +network_scenario_id = 'network_scenario_id_example' # str | Network scenario to be used + +try: + # Deactivate the Network Scenario. + api_instance.sandbox_network_scenario_delete(sandbox_name, network_scenario_id) +except ApiException as e: + print("Exception when calling SandboxNetworkScenariosApi->sandbox_network_scenario_delete: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + **network_scenario_id** | **str**| Network scenario to be used | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_network_scenario_post** +> sandbox_network_scenario_post(sandbox_name, network_scenario_id) + +Selects the Network Scenario to be activated. + +This method selects the network scenario to be activated. This request initiates the creation of necessary MEC services for specific network scenario + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxNetworkScenariosApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier +network_scenario_id = 'network_scenario_id_example' # str | Network scenario to be used + +try: + # Selects the Network Scenario to be activated. + api_instance.sandbox_network_scenario_post(sandbox_name, network_scenario_id) +except ApiException as e: + print("Exception when calling SandboxNetworkScenariosApi->sandbox_network_scenario_post: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + **network_scenario_id** | **str**| Network scenario to be used | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_network_scenarios_get** +> list[SandboxNetworkScenario] sandbox_network_scenarios_get(sandbox_name) + +Get the list of the available network scenarios + +This method retrieves the list of the available network scenarios. + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxNetworkScenariosApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Get the list of the available network scenarios + api_response = api_instance.sandbox_network_scenarios_get(sandbox_name) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxNetworkScenariosApi->sandbox_network_scenarios_get: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +[**list[SandboxNetworkScenario]**](SandboxNetworkScenario.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/SandboxUEControllerApi.md b/examples/demo9/python/mecapp/docs/SandboxUEControllerApi.md new file mode 100644 index 0000000000000000000000000000000000000000..672ef6afe81637994edf8e026043f200c6b2d3e4 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/SandboxUEControllerApi.md @@ -0,0 +1,108 @@ +# swagger_client.SandboxUEControllerApi + +All URIs are relative to *http://localhost/sandbox-api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**sandbox_ue_controller_get**](SandboxUEControllerApi.md#sandbox_ue_controller_get) | **GET** /sandboxUeController/{sandbox_name} | Get the list of the available UEs (e.g. \"Stationary UE\") +[**sandbox_ue_controller_patch**](SandboxUEControllerApi.md#sandbox_ue_controller_patch) | **PATCH** /sandboxUeController/{sandbox_name} | set the new value of the UE + +# **sandbox_ue_controller_get** +> list[UE] sandbox_ue_controller_get(sandbox_name) + +Get the list of the available UEs (e.g. \"Stationary UE\") + +This method retrieves the list of the available available UEs. + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxUEControllerApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier + +try: + # Get the list of the available UEs (e.g. \"Stationary UE\") + api_response = api_instance.sandbox_ue_controller_get(sandbox_name) + pprint(api_response) +except ApiException as e: + print("Exception when calling SandboxUEControllerApi->sandbox_ue_controller_get: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + +### Return type + +[**list[UE]**](UE.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **sandbox_ue_controller_patch** +> sandbox_ue_controller_patch(sandbox_name, user_equipment_id, user_equipment_value) + +set the new value of the UE + +This method sets the new value of the UE. + +### Example +```python +from __future__ import print_function +import time +import swagger_client +from swagger_client.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = swagger_client.SandboxUEControllerApi() +sandbox_name = 'sandbox_name_example' # str | Sandbox identifier +user_equipment_id = 'user_equipment_id_example' # str | User equipmenet identifier +user_equipment_value = 56 # int | It uniquely identifies a UE to set the new value + +try: + # set the new value of the UE + api_instance.sandbox_ue_controller_patch(sandbox_name, user_equipment_id, user_equipment_value) +except ApiException as e: + print("Exception when calling SandboxUEControllerApi->sandbox_ue_controller_patch: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **sandbox_name** | **str**| Sandbox identifier | + **user_equipment_id** | **str**| User equipmenet identifier | + **user_equipment_value** | **int**| It uniquely identifies a UE to set the new value | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Scenario.md b/examples/demo9/python/mecapp/docs/Scenario.md new file mode 100644 index 0000000000000000000000000000000000000000..7e20fa4c26d966159754e7ce18381c3c0f7de88c --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Scenario.md @@ -0,0 +1,14 @@ +# Scenario + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**version** | **str** | Scenario version | [optional] +**id** | **str** | Unique scenario ID | [optional] +**name** | **str** | Unique scenario name | [optional] +**description** | **str** | User description of the scenario. | [optional] +**config** | [**ScenarioConfig**](ScenarioConfig.md) | | [optional] +**deployment** | [**Deployment**](Deployment.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/ScenarioConfig.md b/examples/demo9/python/mecapp/docs/ScenarioConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..49b5204f05960300bb3a942a1d9f182ee984892b --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ScenarioConfig.md @@ -0,0 +1,10 @@ +# ScenarioConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**visualization** | **str** | Visualization configuration | [optional] +**other** | **str** | Other scenario configuration | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/ServiceConfig.md b/examples/demo9/python/mecapp/docs/ServiceConfig.md new file mode 100644 index 0000000000000000000000000000000000000000..46344e74b4b11f847277ca2a6b1b1513c9b930cc --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ServiceConfig.md @@ -0,0 +1,11 @@ +# ServiceConfig + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | Unique service name | [optional] +**me_svc_name** | **str** | Multi-Edge service name, if any | [optional] +**ports** | [**list[ServicePort]**](ServicePort.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/ServicePort.md b/examples/demo9/python/mecapp/docs/ServicePort.md new file mode 100644 index 0000000000000000000000000000000000000000..7ff6cfecd4ce60b555193f6a2588aa1950e5a1df --- /dev/null +++ b/examples/demo9/python/mecapp/docs/ServicePort.md @@ -0,0 +1,11 @@ +# ServicePort + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**protocol** | **str** | Protocol that the application is using (TCP or UDP) | [optional] +**port** | **int** | Port number that the service is listening on | [optional] +**external_port** | **int** | External port number on which to expose the application (30000 - 32767) <li>Only one application allowed per external port <li>Scenario builder must configure to prevent conflicts | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/UE.md b/examples/demo9/python/mecapp/docs/UE.md new file mode 100644 index 0000000000000000000000000000000000000000..b8cdef1c0607f93df8c7071ae54a103c03fa5618 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/UE.md @@ -0,0 +1,9 @@ +# UE + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | The UE name. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/docs/Zone.md b/examples/demo9/python/mecapp/docs/Zone.md new file mode 100644 index 0000000000000000000000000000000000000000..9933c7bc698ebc65356a19886bd5248862c01763 --- /dev/null +++ b/examples/demo9/python/mecapp/docs/Zone.md @@ -0,0 +1,27 @@ +# Zone + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **str** | Unique zone ID | [optional] +**name** | **str** | Zone name | [optional] +**type** | **str** | Zone type | [optional] +**net_char** | [**NetworkCharacteristics**](NetworkCharacteristics.md) | | [optional] +**inter_fog_latency** | **int** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_fog_latency_variation** | **int** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_fog_throughput** | **int** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_fog_packet_loss** | **float** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_edge_latency** | **int** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_edge_latency_variation** | **int** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_edge_throughput** | **int** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**inter_edge_packet_loss** | **float** | **DEPRECATED** As of release 1.3.0, no longer supported | [optional] +**edge_fog_latency** | **int** | **DEPRECATED** As of release 1.3.0, replaced by netChar latency | [optional] +**edge_fog_latency_variation** | **int** | **DEPRECATED** As of release 1.3.0, replaced by netChar latencyVariation | [optional] +**edge_fog_throughput** | **int** | **DEPRECATED** As of release 1.3.0, replaced by netChar throughput | [optional] +**edge_fog_packet_loss** | **float** | **DEPRECATED** As of release 1.3.0, replaced by netChar packetLoss | [optional] +**meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**user_meta** | **dict(str, str)** | Key/Value Pair Map (string, string) | [optional] +**network_locations** | [**list[NetworkLocation]**](NetworkLocation.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/examples/demo9/python/mecapp/git_push.sh b/examples/demo9/python/mecapp/git_push.sh new file mode 100644 index 0000000000000000000000000000000000000000..ae01b182ae9eb047d0999a496b060e62d7b01e5c --- /dev/null +++ b/examples/demo9/python/mecapp/git_push.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/examples/demo9/python/mecapp/requirements.txt b/examples/demo9/python/mecapp/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..bafdc07532f5e1287a24c2af278f547091a410b6 --- /dev/null +++ b/examples/demo9/python/mecapp/requirements.txt @@ -0,0 +1,5 @@ +certifi >= 14.05.14 +six >= 1.10 +python_dateutil >= 2.5.3 +setuptools >= 21.0.0 +urllib3 >= 1.15.1 diff --git a/examples/demo9/python/mecapp/setup.py b/examples/demo9/python/mecapp/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..8702e8b5d6fb563e8e017d150430a8849fa9fdad --- /dev/null +++ b/examples/demo9/python/mecapp/setup.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from setuptools import setup, find_packages # noqa: H301 + +NAME = "swagger-client" +VERSION = "1.0.0" +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = ["urllib3 >= 1.15", "six >= 1.10", "certifi", "python-dateutil"] + +setup( + name=NAME, + version=VERSION, + description="MEC Sandbox API", + author_email="cti_support@etsi.org", + url="", + keywords=["Swagger", "MEC Sandbox API"], + install_requires=REQUIRES, + packages=find_packages(), + include_package_data=True, + long_description="""\ + The MEC Sandbox API described using OpenAPI # noqa: E501 + """ +) diff --git a/examples/demo9/python/mecapp/swagger_client/__init__.py b/examples/demo9/python/mecapp/swagger_client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7814929a1ca8bec258334f49067f3de58e517539 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/__init__.py @@ -0,0 +1,64 @@ +# coding: utf-8 + +# flake8: noqa + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +# import apis into sdk package +from swagger_client.api.authorization_api import AuthorizationApi +from swagger_client.api.sandbox_app_instances_api import SandboxAppInstancesApi +from swagger_client.api.sandbox_logs_subscriptions_api import SandboxLogsSubscriptionsApi +from swagger_client.api.sandbox_mec_services_api import SandboxMECServicesApi +from swagger_client.api.sandbox_network_scenarios_api import SandboxNetworkScenariosApi +from swagger_client.api.sandbox_ue_controller_api import SandboxUEControllerApi +# import ApiClient +from swagger_client.api_client import ApiClient +from swagger_client.configuration import Configuration +# import models into sdk package +from swagger_client.models.application_info import ApplicationInfo +from swagger_client.models.cellular_domain_config import CellularDomainConfig +from swagger_client.models.cellular_poa_config import CellularPoaConfig +from swagger_client.models.connectivity_config import ConnectivityConfig +from swagger_client.models.cpu_config import CpuConfig +from swagger_client.models.d2d_config import D2dConfig +from swagger_client.models.dn_config import DNConfig +from swagger_client.models.deployment import Deployment +from swagger_client.models.domain import Domain +from swagger_client.models.egress_service import EgressService +from swagger_client.models.external_config import ExternalConfig +from swagger_client.models.geo_data import GeoData +from swagger_client.models.gpu_config import GpuConfig +from swagger_client.models.ingress_service import IngressService +from swagger_client.models.line_string import LineString +from swagger_client.models.memory_config import MemoryConfig +from swagger_client.models.namespace import Namespace +from swagger_client.models.network_characteristics import NetworkCharacteristics +from swagger_client.models.network_location import NetworkLocation +from swagger_client.models.oauth import Oauth +from swagger_client.models.physical_location import PhysicalLocation +from swagger_client.models.poa4_g_config import Poa4GConfig +from swagger_client.models.poa5_g_config import Poa5GConfig +from swagger_client.models.poa_wifi_config import PoaWifiConfig +from swagger_client.models.point import Point +from swagger_client.models.problem_details import ProblemDetails +from swagger_client.models.process import Process +from swagger_client.models.sandbox_app_instances import SandboxAppInstances +from swagger_client.models.sandbox_logs_subscriptions import SandboxLogsSubscriptions +from swagger_client.models.sandbox_mec_services import SandboxMecServices +from swagger_client.models.sandbox_network_scenario import SandboxNetworkScenario +from swagger_client.models.scenario import Scenario +from swagger_client.models.scenario_config import ScenarioConfig +from swagger_client.models.service_config import ServiceConfig +from swagger_client.models.service_port import ServicePort +from swagger_client.models.ue import UE +from swagger_client.models.zone import Zone diff --git a/examples/demo9/python/mecapp/swagger_client/api/__init__.py b/examples/demo9/python/mecapp/swagger_client/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..669ebe1301ebfd40c12677eb6e73b179fc53609d --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/__init__.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +# flake8: noqa + +# import apis into api package +from swagger_client.api.authorization_api import AuthorizationApi +from swagger_client.api.sandbox_app_instances_api import SandboxAppInstancesApi +from swagger_client.api.sandbox_logs_subscriptions_api import SandboxLogsSubscriptionsApi +from swagger_client.api.sandbox_mec_services_api import SandboxMECServicesApi +from swagger_client.api.sandbox_network_scenarios_api import SandboxNetworkScenariosApi +from swagger_client.api.sandbox_ue_controller_api import SandboxUEControllerApi diff --git a/examples/demo9/python/mecapp/swagger_client/api/authorization_api.py b/examples/demo9/python/mecapp/swagger_client/api/authorization_api.py new file mode 100644 index 0000000000000000000000000000000000000000..f07fedafa75e5cb94d80c971d63d5124c5608681 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/authorization_api.py @@ -0,0 +1,314 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from swagger_client.api_client import ApiClient + + +class AuthorizationApi(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def get_namespace(self, user_code, **kwargs): # noqa: E501 + """Get the namespace against the User Code # noqa: E501 + + Get the namespace against the User Code # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_namespace(user_code, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str user_code: User Code obtained from the login endpoint (required) + :return: Namespace + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_namespace_with_http_info(user_code, **kwargs) # noqa: E501 + else: + (data) = self.get_namespace_with_http_info(user_code, **kwargs) # noqa: E501 + return data + + def get_namespace_with_http_info(self, user_code, **kwargs): # noqa: E501 + """Get the namespace against the User Code # noqa: E501 + + Get the namespace against the User Code # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_namespace_with_http_info(user_code, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str user_code: User Code obtained from the login endpoint (required) + :return: Namespace + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['user_code'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method get_namespace" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'user_code' is set + if ('user_code' not in params or + params['user_code'] is None): + raise ValueError("Missing the required parameter `user_code` when calling `get_namespace`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + query_params = [] + if 'user_code' in params: + query_params.append(('user_code', params['user_code'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/namespace', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='Namespace', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def login(self, provider, **kwargs): # noqa: E501 + """Initiate OAuth login procedure and creates a MEC Sandbox instance # noqa: E501 + + Initiate OAuth login procedure and creates a MEC Sandbox instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.login(provider, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str provider: Oauth provider (required) + :return: Oauth + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.login_with_http_info(provider, **kwargs) # noqa: E501 + else: + (data) = self.login_with_http_info(provider, **kwargs) # noqa: E501 + return data + + def login_with_http_info(self, provider, **kwargs): # noqa: E501 + """Initiate OAuth login procedure and creates a MEC Sandbox instance # noqa: E501 + + Initiate OAuth login procedure and creates a MEC Sandbox instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.login_with_http_info(provider, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str provider: Oauth provider (required) + :return: Oauth + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['provider'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method login" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'provider' is set + if ('provider' not in params or + params['provider'] is None): + raise ValueError("Missing the required parameter `provider` when calling `login`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + query_params = [] + if 'provider' in params: + query_params.append(('provider', params['provider'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/login', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='Oauth', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def logout(self, sandbox_name, **kwargs): # noqa: E501 + """Terminates User Session and delete the Sandbox instance # noqa: E501 + + Terminates User Session and delete the Sandbox instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.logout(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.logout_with_http_info(sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.logout_with_http_info(sandbox_name, **kwargs) # noqa: E501 + return data + + def logout_with_http_info(self, sandbox_name, **kwargs): # noqa: E501 + """Terminates User Session and delete the Sandbox instance # noqa: E501 + + Terminates User Session and delete the Sandbox instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.logout_with_http_info(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method logout" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `logout`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + query_params = [] + if 'sandbox_name' in params: + query_params.append(('sandbox_name', params['sandbox_name'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/logout', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/examples/demo9/python/mecapp/swagger_client/api/sandbox_app_instances_api.py b/examples/demo9/python/mecapp/swagger_client/api/sandbox_app_instances_api.py new file mode 100644 index 0000000000000000000000000000000000000000..898f46320945723cf30646823ab04842829b30ff --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/sandbox_app_instances_api.py @@ -0,0 +1,334 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from swagger_client.api_client import ApiClient + + +class SandboxAppInstancesApi(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def sandbox_app_instances_delete(self, sandbox_name, app_instance_id, **kwargs): # noqa: E501 + """Delete an existing application instance # noqa: E501 + + This method removes an existing application instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_app_instances_delete(sandbox_name, app_instance_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str app_instance_id: It uniquely identifies a MEC application instance identifier (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_app_instances_delete_with_http_info(sandbox_name, app_instance_id, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_app_instances_delete_with_http_info(sandbox_name, app_instance_id, **kwargs) # noqa: E501 + return data + + def sandbox_app_instances_delete_with_http_info(self, sandbox_name, app_instance_id, **kwargs): # noqa: E501 + """Delete an existing application instance # noqa: E501 + + This method removes an existing application instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_app_instances_delete_with_http_info(sandbox_name, app_instance_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str app_instance_id: It uniquely identifies a MEC application instance identifier (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name', 'app_instance_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_app_instances_delete" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_app_instances_delete`") # noqa: E501 + # verify the required parameter 'app_instance_id' is set + if ('app_instance_id' not in params or + params['app_instance_id'] is None): + raise ValueError("Missing the required parameter `app_instance_id` when calling `sandbox_app_instances_delete`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + if 'app_instance_id' in params: + path_params['app_instance_id'] = params['app_instance_id'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxAppInstances/{sandbox_name}/{app_instance_id}', 'DELETE', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_app_instances_get(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available application instance identifiers # noqa: E501 + + This method retrieves the list of the available application instance identifiers # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_app_instances_get(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxAppInstances] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_app_instances_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_app_instances_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + return data + + def sandbox_app_instances_get_with_http_info(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available application instance identifiers # noqa: E501 + + This method retrieves the list of the available application instance identifiers # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_app_instances_get_with_http_info(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxAppInstances] + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_app_instances_get" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_app_instances_get`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxAppInstances/{sandbox_name}', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[SandboxAppInstances]', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_app_instances_post(self, body, sandbox_name, **kwargs): # noqa: E501 + """Create a new application instance identifier # noqa: E501 + + This method creates a new application instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_app_instances_post(body, sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param ApplicationInfo body: The application description (required) + :param str sandbox_name: Sandbox identifier (required) + :return: ApplicationInfo + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_app_instances_post_with_http_info(body, sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_app_instances_post_with_http_info(body, sandbox_name, **kwargs) # noqa: E501 + return data + + def sandbox_app_instances_post_with_http_info(self, body, sandbox_name, **kwargs): # noqa: E501 + """Create a new application instance identifier # noqa: E501 + + This method creates a new application instance # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_app_instances_post_with_http_info(body, sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param ApplicationInfo body: The application description (required) + :param str sandbox_name: Sandbox identifier (required) + :return: ApplicationInfo + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['body', 'sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_app_instances_post" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'body' is set + if ('body' not in params or + params['body'] is None): + raise ValueError("Missing the required parameter `body` when calling `sandbox_app_instances_post`") # noqa: E501 + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_app_instances_post`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in params: + body_params = params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxAppInstances/{sandbox_name}', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='ApplicationInfo', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/examples/demo9/python/mecapp/swagger_client/api/sandbox_logs_subscriptions_api.py b/examples/demo9/python/mecapp/swagger_client/api/sandbox_logs_subscriptions_api.py new file mode 100644 index 0000000000000000000000000000000000000000..4904e2d4d1768e26ad33f627bbd3c43d5ff73732 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/sandbox_logs_subscriptions_api.py @@ -0,0 +1,227 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from swagger_client.api_client import ApiClient + + +class SandboxLogsSubscriptionsApi(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def sandbox_logs_subscriptions_delete(self, sandbox_name, subscription_reference, **kwargs): # noqa: E501 + """Subscription to receive logs from the sandbox # noqa: E501 + + This method is used to receive logs from the sandbox. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_logs_subscriptions_delete(sandbox_name, subscription_reference, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str subscription_reference: It uniquely identifies subscription reference to receive logs from the sandbox (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_logs_subscriptions_delete_with_http_info(sandbox_name, subscription_reference, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_logs_subscriptions_delete_with_http_info(sandbox_name, subscription_reference, **kwargs) # noqa: E501 + return data + + def sandbox_logs_subscriptions_delete_with_http_info(self, sandbox_name, subscription_reference, **kwargs): # noqa: E501 + """Subscription to receive logs from the sandbox # noqa: E501 + + This method is used to receive logs from the sandbox. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_logs_subscriptions_delete_with_http_info(sandbox_name, subscription_reference, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str subscription_reference: It uniquely identifies subscription reference to receive logs from the sandbox (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name', 'subscription_reference'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_logs_subscriptions_delete" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_logs_subscriptions_delete`") # noqa: E501 + # verify the required parameter 'subscription_reference' is set + if ('subscription_reference' not in params or + params['subscription_reference'] is None): + raise ValueError("Missing the required parameter `subscription_reference` when calling `sandbox_logs_subscriptions_delete`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + if 'subscription_reference' in params: + path_params['subscription_reference'] = params['subscription_reference'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxLogsSubscriptions/{sandbox_name}/{subscription_reference}', 'DELETE', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_logs_subscriptions_post(self, sandbox_name, **kwargs): # noqa: E501 + """Subscription to receive logs from the sandbox # noqa: E501 + + This method is used to receive logs from the sandbox. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_logs_subscriptions_post(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxLogsSubscriptions] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_logs_subscriptions_post_with_http_info(sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_logs_subscriptions_post_with_http_info(sandbox_name, **kwargs) # noqa: E501 + return data + + def sandbox_logs_subscriptions_post_with_http_info(self, sandbox_name, **kwargs): # noqa: E501 + """Subscription to receive logs from the sandbox # noqa: E501 + + This method is used to receive logs from the sandbox. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_logs_subscriptions_post_with_http_info(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxLogsSubscriptions] + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_logs_subscriptions_post" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_logs_subscriptions_post`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxLogsSubscriptions/{sandbox_name}', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[SandboxLogsSubscriptions]', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/examples/demo9/python/mecapp/swagger_client/api/sandbox_mec_services_api.py b/examples/demo9/python/mecapp/swagger_client/api/sandbox_mec_services_api.py new file mode 100644 index 0000000000000000000000000000000000000000..8286a37ead1c8f8cef6b73fec7b6cf57e4809dd1 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/sandbox_mec_services_api.py @@ -0,0 +1,128 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from swagger_client.api_client import ApiClient + + +class SandboxMECServicesApi(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def sandbox_mec_services_get(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available MEC services # noqa: E501 + + This method retrieves the list of the available MEC services. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_mec_services_get(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxMecServices] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_mec_services_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_mec_services_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + return data + + def sandbox_mec_services_get_with_http_info(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available MEC services # noqa: E501 + + This method retrieves the list of the available MEC services. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_mec_services_get_with_http_info(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxMecServices] + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_mec_services_get" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_mec_services_get`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxMecServices/{sandbox_name}', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[SandboxMecServices]', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/examples/demo9/python/mecapp/swagger_client/api/sandbox_network_scenarios_api.py b/examples/demo9/python/mecapp/swagger_client/api/sandbox_network_scenarios_api.py new file mode 100644 index 0000000000000000000000000000000000000000..05dc3d666ec56c1b7f1e359557061aafefee5798 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/sandbox_network_scenarios_api.py @@ -0,0 +1,429 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from swagger_client.api_client import ApiClient + + +class SandboxNetworkScenariosApi(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def sandbox_individual_network_scenarios_get(self, sandbox_name, network_scenario_id, **kwargs): # noqa: E501 + """Get description of a Network Scenario to be used. # noqa: E501 + + This method retrive description of a the network scenario # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_individual_network_scenarios_get(sandbox_name, network_scenario_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str network_scenario_id: Network scenario to retrieve (required) + :return: list[Scenario] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_individual_network_scenarios_get_with_http_info(sandbox_name, network_scenario_id, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_individual_network_scenarios_get_with_http_info(sandbox_name, network_scenario_id, **kwargs) # noqa: E501 + return data + + def sandbox_individual_network_scenarios_get_with_http_info(self, sandbox_name, network_scenario_id, **kwargs): # noqa: E501 + """Get description of a Network Scenario to be used. # noqa: E501 + + This method retrive description of a the network scenario # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_individual_network_scenarios_get_with_http_info(sandbox_name, network_scenario_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str network_scenario_id: Network scenario to retrieve (required) + :return: list[Scenario] + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name', 'network_scenario_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_individual_network_scenarios_get" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_individual_network_scenarios_get`") # noqa: E501 + # verify the required parameter 'network_scenario_id' is set + if ('network_scenario_id' not in params or + params['network_scenario_id'] is None): + raise ValueError("Missing the required parameter `network_scenario_id` when calling `sandbox_individual_network_scenarios_get`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + if 'network_scenario_id' in params: + query_params.append(('network_scenario_id', params['network_scenario_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxNetworkScenarios/{sandbox_name}', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[Scenario]', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_network_scenario_delete(self, sandbox_name, network_scenario_id, **kwargs): # noqa: E501 + """Deactivate the Network Scenario. # noqa: E501 + + This method deactivates the network scenario # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_network_scenario_delete(sandbox_name, network_scenario_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str network_scenario_id: Network scenario to be used (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_network_scenario_delete_with_http_info(sandbox_name, network_scenario_id, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_network_scenario_delete_with_http_info(sandbox_name, network_scenario_id, **kwargs) # noqa: E501 + return data + + def sandbox_network_scenario_delete_with_http_info(self, sandbox_name, network_scenario_id, **kwargs): # noqa: E501 + """Deactivate the Network Scenario. # noqa: E501 + + This method deactivates the network scenario # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_network_scenario_delete_with_http_info(sandbox_name, network_scenario_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str network_scenario_id: Network scenario to be used (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name', 'network_scenario_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_network_scenario_delete" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_network_scenario_delete`") # noqa: E501 + # verify the required parameter 'network_scenario_id' is set + if ('network_scenario_id' not in params or + params['network_scenario_id'] is None): + raise ValueError("Missing the required parameter `network_scenario_id` when calling `sandbox_network_scenario_delete`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + if 'network_scenario_id' in params: + path_params['network_scenario_id'] = params['network_scenario_id'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxNetworkScenarios/{sandbox_name}/{network_scenario_id}', 'DELETE', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_network_scenario_post(self, sandbox_name, network_scenario_id, **kwargs): # noqa: E501 + """Selects the Network Scenario to be activated. # noqa: E501 + + This method selects the network scenario to be activated. This request initiates the creation of necessary MEC services for specific network scenario # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_network_scenario_post(sandbox_name, network_scenario_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str network_scenario_id: Network scenario to be used (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_network_scenario_post_with_http_info(sandbox_name, network_scenario_id, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_network_scenario_post_with_http_info(sandbox_name, network_scenario_id, **kwargs) # noqa: E501 + return data + + def sandbox_network_scenario_post_with_http_info(self, sandbox_name, network_scenario_id, **kwargs): # noqa: E501 + """Selects the Network Scenario to be activated. # noqa: E501 + + This method selects the network scenario to be activated. This request initiates the creation of necessary MEC services for specific network scenario # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_network_scenario_post_with_http_info(sandbox_name, network_scenario_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str network_scenario_id: Network scenario to be used (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name', 'network_scenario_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_network_scenario_post" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_network_scenario_post`") # noqa: E501 + # verify the required parameter 'network_scenario_id' is set + if ('network_scenario_id' not in params or + params['network_scenario_id'] is None): + raise ValueError("Missing the required parameter `network_scenario_id` when calling `sandbox_network_scenario_post`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + if 'network_scenario_id' in params: + query_params.append(('network_scenario_id', params['network_scenario_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxNetworkScenarios/{sandbox_name}', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_network_scenarios_get(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available network scenarios # noqa: E501 + + This method retrieves the list of the available network scenarios. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_network_scenarios_get(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxNetworkScenario] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_network_scenarios_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_network_scenarios_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + return data + + def sandbox_network_scenarios_get_with_http_info(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available network scenarios # noqa: E501 + + This method retrieves the list of the available network scenarios. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_network_scenarios_get_with_http_info(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[SandboxNetworkScenario] + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_network_scenarios_get" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_network_scenarios_get`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + query_params = [] + if 'sandbox_name' in params: + query_params.append(('sandbox_name', params['sandbox_name'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxNetworkScenarios', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[SandboxNetworkScenario]', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/examples/demo9/python/mecapp/swagger_client/api/sandbox_ue_controller_api.py b/examples/demo9/python/mecapp/swagger_client/api/sandbox_ue_controller_api.py new file mode 100644 index 0000000000000000000000000000000000000000..3755640ccbd5cb6b8972cb2e9ea2990ff7e0dca9 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api/sandbox_ue_controller_api.py @@ -0,0 +1,235 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from swagger_client.api_client import ApiClient + + +class SandboxUEControllerApi(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def sandbox_ue_controller_get(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available UEs (e.g. \"Stationary UE\") # noqa: E501 + + This method retrieves the list of the available available UEs. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_ue_controller_get(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[UE] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_ue_controller_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_ue_controller_get_with_http_info(sandbox_name, **kwargs) # noqa: E501 + return data + + def sandbox_ue_controller_get_with_http_info(self, sandbox_name, **kwargs): # noqa: E501 + """Get the list of the available UEs (e.g. \"Stationary UE\") # noqa: E501 + + This method retrieves the list of the available available UEs. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_ue_controller_get_with_http_info(sandbox_name, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :return: list[UE] + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_ue_controller_get" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_ue_controller_get`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxUeController/{sandbox_name}', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[UE]', # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) + + def sandbox_ue_controller_patch(self, sandbox_name, user_equipment_id, user_equipment_value, **kwargs): # noqa: E501 + """set the new value of the UE # noqa: E501 + + This method sets the new value of the UE. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_ue_controller_patch(sandbox_name, user_equipment_id, user_equipment_value, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str user_equipment_id: User equipmenet identifier (required) + :param int user_equipment_value: It uniquely identifies a UE to set the new value (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.sandbox_ue_controller_patch_with_http_info(sandbox_name, user_equipment_id, user_equipment_value, **kwargs) # noqa: E501 + else: + (data) = self.sandbox_ue_controller_patch_with_http_info(sandbox_name, user_equipment_id, user_equipment_value, **kwargs) # noqa: E501 + return data + + def sandbox_ue_controller_patch_with_http_info(self, sandbox_name, user_equipment_id, user_equipment_value, **kwargs): # noqa: E501 + """set the new value of the UE # noqa: E501 + + This method sets the new value of the UE. # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.sandbox_ue_controller_patch_with_http_info(sandbox_name, user_equipment_id, user_equipment_value, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str sandbox_name: Sandbox identifier (required) + :param str user_equipment_id: User equipmenet identifier (required) + :param int user_equipment_value: It uniquely identifies a UE to set the new value (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + all_params = ['sandbox_name', 'user_equipment_id', 'user_equipment_value'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method sandbox_ue_controller_patch" % key + ) + params[key] = val + del params['kwargs'] + # verify the required parameter 'sandbox_name' is set + if ('sandbox_name' not in params or + params['sandbox_name'] is None): + raise ValueError("Missing the required parameter `sandbox_name` when calling `sandbox_ue_controller_patch`") # noqa: E501 + # verify the required parameter 'user_equipment_id' is set + if ('user_equipment_id' not in params or + params['user_equipment_id'] is None): + raise ValueError("Missing the required parameter `user_equipment_id` when calling `sandbox_ue_controller_patch`") # noqa: E501 + # verify the required parameter 'user_equipment_value' is set + if ('user_equipment_value' not in params or + params['user_equipment_value'] is None): + raise ValueError("Missing the required parameter `user_equipment_value` when calling `sandbox_ue_controller_patch`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'sandbox_name' in params: + path_params['sandbox_name'] = params['sandbox_name'] # noqa: E501 + + query_params = [] + if 'user_equipment_id' in params: + query_params.append(('user_equipment_id', params['user_equipment_id'])) # noqa: E501 + if 'user_equipment_value' in params: + query_params.append(('user_equipment_value', params['user_equipment_value'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/sandboxUeController/{sandbox_name}', 'PATCH', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/examples/demo9/python/mecapp/swagger_client/api_client.py b/examples/demo9/python/mecapp/swagger_client/api_client.py new file mode 100644 index 0000000000000000000000000000000000000000..a72b51c02f12a819d5c9941a924b5c765ced58c1 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/api_client.py @@ -0,0 +1,634 @@ +# coding: utf-8 +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" +from __future__ import absolute_import + +import datetime +import json +import mimetypes +from multiprocessing.pool import ThreadPool +import os +import re +import tempfile + +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import quote + +from swagger_client.configuration import Configuration +import swagger_client.models +from swagger_client import rest + + +class ApiClient(object): + """Generic API client for Swagger client library builds. + + Swagger generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the Swagger + templates. + + NOTE: This class is auto generated by the swagger code generator program. + Ref: https://github.com/swagger-api/swagger-codegen + Do not edit the class manually. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int if six.PY3 else long, # noqa: F821 + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'object': object, + } + + def __init__(self, configuration=None, header_name=None, header_value=None, + cookie=None): + if configuration is None: + configuration = Configuration() + self.configuration = configuration + + self.pool = ThreadPool() + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = 'Swagger-Codegen/1.0.0/python' + + def __del__(self): + self.pool.close() + self.pool.join() + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + def __call_api( + self, resource_path, method, path_params=None, + query_params=None, header_params=None, body=None, post_params=None, + files=None, response_type=None, auth_settings=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict(self.parameters_to_tuples(header_params, + collection_formats)) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples(path_params, + collection_formats) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + query_params = self.parameters_to_tuples(query_params, + collection_formats) + + # post parameters + if post_params or files: + post_params = self.prepare_post_parameters(post_params, files) + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples(post_params, + collection_formats) + + # auth setting + self.update_params_for_auth(header_params, query_params, auth_settings) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + url = self.configuration.host + resource_path + + # perform request and return response + response_data = self.request( + method, url, query_params=query_params, headers=header_params, + post_params=post_params, body=body, + _preload_content=_preload_content, + _request_timeout=_request_timeout) + + self.last_response = response_data + + return_data = response_data + if _preload_content: + # deserialize response data + if response_type: + return_data = self.deserialize(response_data, response_type) + else: + #return_data = None + return (return_data, response_data.status, + response_data.getheaders()) # ETSI STF 678 (FSCOM) + + if _return_http_data_only: + return (return_data) + else: + return (return_data, response_data.status, + response_data.getheaders()) + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is swagger model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [self.sanitize_for_serialization(sub_obj) + for sub_obj in obj] + elif isinstance(obj, tuple): + return tuple(self.sanitize_for_serialization(sub_obj) + for sub_obj in obj) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + + if isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `swagger_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + obj_dict = {obj.attribute_map[attr]: getattr(obj, attr) + for attr, _ in six.iteritems(obj.swagger_types) + if getattr(obj, attr) is not None} + + return {key: self.sanitize_for_serialization(val) + for key, val in six.iteritems(obj_dict)} + + def deserialize(self, response, response_type): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + + :return: deserialized object. + """ + # handle file downloading + # save response body into a tmp file and return the instance + if response_type == "file": + return self.__deserialize_file(response) + + # fetch data from response object + try: + data = json.loads(response.data) + except ValueError: + data = response.data + + return self.__deserialize(data, response_type) + + def __deserialize(self, data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if type(klass) == str: + if klass.startswith('list['): + sub_kls = re.match(r'list\[(.*)\]', klass).group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('dict('): + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in six.iteritems(data)} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr(swagger_client.models, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datatime(data) + else: + return self.__deserialize_model(data, klass) + + def call_api(self, resource_path, method, + path_params=None, query_params=None, header_params=None, + body=None, post_params=None, files=None, + response_type=None, auth_settings=None, async_req=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + """Makes the HTTP request (synchronous) and returns deserialized data. + + To make an async request, set the async_req parameter. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param response: Response data type. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param async_req bool: execute request asynchronously + :param _return_http_data_only: response data without head status code + and headers + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: + If async_req parameter is True, + the request will be called asynchronously. + The method will return the request thread. + If parameter async_req is False or missing, + then the method will return the response directly. + """ + if not async_req: + return self.__call_api(resource_path, method, + path_params, query_params, header_params, + body, post_params, files, + response_type, auth_settings, + _return_http_data_only, collection_formats, + _preload_content, _request_timeout) + else: + thread = self.pool.apply_async(self.__call_api, (resource_path, + method, path_params, query_params, + header_params, body, + post_params, files, + response_type, auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, _request_timeout)) + return thread + + def request(self, method, url, query_params=None, headers=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + """Makes the HTTP request using RESTClient.""" + if method == "GET": + return self.rest_client.GET(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "HEAD": + return self.rest_client.HEAD(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "OPTIONS": + return self.rest_client.OPTIONS(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "POST": + return self.rest_client.POST(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PUT": + return self.rest_client.PUT(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PATCH": + return self.rest_client.PATCH(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "DELETE": + return self.rest_client.DELETE(url, + query_params=query_params, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + else: + raise ValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params = [] + if collection_formats is None: + collection_formats = {} + for k, v in six.iteritems(params) if isinstance(params, dict) else params: # noqa: E501 + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def prepare_post_parameters(self, post_params=None, files=None): + """Builds form parameters. + + :param post_params: Normal form parameters. + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + + if post_params: + params = post_params + + if files: + for k, v in six.iteritems(files): + if not v: + continue + file_names = v if type(v) is list else [v] + for n in file_names: + with open(n, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + mimetype = (mimetypes.guess_type(filename)[0] or + 'application/octet-stream') + params.append( + tuple([k, tuple([filename, filedata, mimetype])])) + + return params + + def select_header_accept(self, accepts): + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return + + accepts = [x.lower() for x in accepts] + + if 'application/json' in accepts: + return 'application/json' + else: + return ', '.join(accepts) + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return 'application/json' + + content_types = [x.lower() for x in content_types] + + if 'application/json' in content_types or '*/*' in content_types: + return 'application/json' + else: + return content_types[0] + + def update_params_for_auth(self, headers, querys, auth_settings): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param querys: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + """ + if not auth_settings: + return + + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + if not auth_setting['value']: + continue + elif auth_setting['in'] == 'header': + headers[auth_setting['key']] = auth_setting['value'] + elif auth_setting['in'] == 'query': + querys.append((auth_setting['key'], auth_setting['value'])) + else: + raise ValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.getheader("Content-Disposition") + if content_disposition: + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition).group(1) + path = os.path.join(os.path.dirname(path), filename) + response_data = response.data + with open(path, "wb") as f: + if isinstance(response_data, str): + # change str to bytes so we can write it + response_data = response_data.encode('utf-8') + f.write(response_data) + else: + f.write(response_data) + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return six.text_type(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return a original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datatime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __hasattr(self, object, name): + return name in object.__class__.__dict__ + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + if not klass.swagger_types and not self.__hasattr(klass, 'get_real_child_model'): + return data + + kwargs = {} + if klass.swagger_types is not None: + for attr, attr_type in six.iteritems(klass.swagger_types): + if (data is not None and + klass.attribute_map[attr] in data and + isinstance(data, (list, dict))): + value = data[klass.attribute_map[attr]] + kwargs[attr] = self.__deserialize(value, attr_type) + + instance = klass(**kwargs) + + if (isinstance(instance, dict) and + klass.swagger_types is not None and + isinstance(data, dict)): + for key, value in data.items(): + if key not in klass.swagger_types: + instance[key] = value + if self.__hasattr(instance, 'get_real_child_model'): + klass_name = instance.get_real_child_model(data) + if klass_name: + instance = self.__deserialize(data, klass_name) + return instance diff --git a/examples/demo9/python/mecapp/swagger_client/configuration.py b/examples/demo9/python/mecapp/swagger_client/configuration.py new file mode 100644 index 0000000000000000000000000000000000000000..97eb12329f95ca062e291c2025caea9f3e47d6c5 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/configuration.py @@ -0,0 +1,244 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import copy +import logging +import multiprocessing +import sys +import urllib3 + +import six +from six.moves import http_client as httplib + + +class TypeWithDefault(type): + def __init__(cls, name, bases, dct): + super(TypeWithDefault, cls).__init__(name, bases, dct) + cls._default = None + + def __call__(cls): + if cls._default is None: + cls._default = type.__call__(cls) + return copy.copy(cls._default) + + def set_default(cls, default): + cls._default = copy.copy(default) + + +class Configuration(six.with_metaclass(TypeWithDefault, object)): + """NOTE: This class is auto generated by the swagger code generator program. + + Ref: https://github.com/swagger-api/swagger-codegen + Do not edit the class manually. + """ + + def __init__(self): + """Constructor""" + # Default Base url + self.host = "http://localhost/sandbox-api/v1" + # Temp file folder for downloading files + self.temp_folder_path = None + + # Authentication Settings + # dict to store API key(s) + self.api_key = {} + # dict to store API prefix (e.g. Bearer) + self.api_key_prefix = {} + # function to refresh API key if expired + self.refresh_api_key_hook = None + # Username for HTTP basic authentication + self.username = "" + # Password for HTTP basic authentication + self.password = "" + # Logging Settings + self.logger = {} + self.logger["package_logger"] = logging.getLogger("swagger_client") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + # Log format + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + # Log stream handler + self.logger_stream_handler = None + # Log file handler + self.logger_file_handler = None + # Debug file location + self.logger_file = None + # Debug switch + self.debug = False + + # SSL/TLS verification + # Set this to false to skip verifying SSL certificate when calling API + # from https server. + self.verify_ssl = True + # Set this to customize the certificate file to verify the peer. + self.ssl_ca_cert = None + # client certificate file + self.cert_file = None + # client key file + self.key_file = None + # Set this to True/False to enable/disable SSL hostname verification. + self.assert_hostname = None + + # urllib3 connection pool's maximum number of connections saved + # per pool. urllib3 uses 1 connection as default value, but this is + # not the best value when you are making a lot of possibly parallel + # requests to the same host, which is often the case here. + # cpu_count * 5 is used as default value to increase performance. + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + + # Proxy URL + self.proxy = None + # Safe chars for path_param + self.safe_chars_for_path_param = '' + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_file_handler) + if self.logger_stream_handler: + logger.removeHandler(self.logger_stream_handler) + else: + # If not set logging file, + # then add stream handler and remove file handler. + self.logger_stream_handler = logging.StreamHandler() + self.logger_stream_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_stream_handler) + if self.logger_file_handler: + logger.removeHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier): + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :return: The token for api key authentication. + """ + if self.refresh_api_key_hook: + self.refresh_api_key_hook(self) + + key = self.api_key.get(identifier) + if key: + prefix = self.api_key_prefix.get(identifier) + if prefix: + return "%s %s" % (prefix, key) + else: + return key + + def get_basic_auth_token(self): + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + return urllib3.util.make_headers( + basic_auth=self.username + ':' + self.password + ).get('authorization') + + def auth_settings(self): + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + return { + } + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: 0.0.9\n"\ + "SDK Package Version: 1.0.0".\ + format(env=sys.platform, pyversion=sys.version) diff --git a/examples/demo9/python/mecapp/swagger_client/models/__init__.py b/examples/demo9/python/mecapp/swagger_client/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ce74c781ad40d948cf167a80b0941da35b99a573 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/__init__.py @@ -0,0 +1,53 @@ +# coding: utf-8 + +# flake8: noqa +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +# import models into model package +from swagger_client.models.application_info import ApplicationInfo +from swagger_client.models.cellular_domain_config import CellularDomainConfig +from swagger_client.models.cellular_poa_config import CellularPoaConfig +from swagger_client.models.connectivity_config import ConnectivityConfig +from swagger_client.models.cpu_config import CpuConfig +from swagger_client.models.d2d_config import D2dConfig +from swagger_client.models.dn_config import DNConfig +from swagger_client.models.deployment import Deployment +from swagger_client.models.domain import Domain +from swagger_client.models.egress_service import EgressService +from swagger_client.models.external_config import ExternalConfig +from swagger_client.models.geo_data import GeoData +from swagger_client.models.gpu_config import GpuConfig +from swagger_client.models.ingress_service import IngressService +from swagger_client.models.line_string import LineString +from swagger_client.models.memory_config import MemoryConfig +from swagger_client.models.namespace import Namespace +from swagger_client.models.network_characteristics import NetworkCharacteristics +from swagger_client.models.network_location import NetworkLocation +from swagger_client.models.oauth import Oauth +from swagger_client.models.physical_location import PhysicalLocation +from swagger_client.models.poa4_g_config import Poa4GConfig +from swagger_client.models.poa5_g_config import Poa5GConfig +from swagger_client.models.poa_wifi_config import PoaWifiConfig +from swagger_client.models.point import Point +from swagger_client.models.problem_details import ProblemDetails +from swagger_client.models.process import Process +from swagger_client.models.sandbox_app_instances import SandboxAppInstances +from swagger_client.models.sandbox_logs_subscriptions import SandboxLogsSubscriptions +from swagger_client.models.sandbox_mec_services import SandboxMecServices +from swagger_client.models.sandbox_network_scenario import SandboxNetworkScenario +from swagger_client.models.scenario import Scenario +from swagger_client.models.scenario_config import ScenarioConfig +from swagger_client.models.service_config import ServiceConfig +from swagger_client.models.service_port import ServicePort +from swagger_client.models.ue import UE +from swagger_client.models.zone import Zone diff --git a/examples/demo9/python/mecapp/swagger_client/models/application_info.py b/examples/demo9/python/mecapp/swagger_client/models/application_info.py new file mode 100644 index 0000000000000000000000000000000000000000..c163eb0340619c2181aa983062ea65a0513d5831 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/application_info.py @@ -0,0 +1,232 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ApplicationInfo(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'name': 'str', + 'node_name': 'str', + 'type': 'str', + 'persist': 'bool' + } + + attribute_map = { + 'id': 'id', + 'name': 'name', + 'node_name': 'nodeName', + 'type': 'type', + 'persist': 'persist' + } + + def __init__(self, id=None, name=None, node_name=None, type=None, persist=None): # noqa: E501 + """ApplicationInfo - a model defined in Swagger""" # noqa: E501 + self._id = None + self._name = None + self._node_name = None + self._type = None + self._persist = None + self.discriminator = None + if id is not None: + self.id = id + self.name = name + self.node_name = node_name + if type is not None: + self.type = type + if persist is not None: + self.persist = persist + + @property + def id(self): + """Gets the id of this ApplicationInfo. # noqa: E501 + + Application Instance UUID # noqa: E501 + + :return: The id of this ApplicationInfo. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this ApplicationInfo. + + Application Instance UUID # noqa: E501 + + :param id: The id of this ApplicationInfo. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this ApplicationInfo. # noqa: E501 + + Application name # noqa: E501 + + :return: The name of this ApplicationInfo. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this ApplicationInfo. + + Application name # noqa: E501 + + :param name: The name of this ApplicationInfo. # noqa: E501 + :type: str + """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 + + self._name = name + + @property + def node_name(self): + """Gets the node_name of this ApplicationInfo. # noqa: E501 + + Name of node where application instance is running # noqa: E501 + + :return: The node_name of this ApplicationInfo. # noqa: E501 + :rtype: str + """ + return self._node_name + + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this ApplicationInfo. + + Name of node where application instance is running # noqa: E501 + + :param node_name: The node_name of this ApplicationInfo. # noqa: E501 + :type: str + """ + if node_name is None: + raise ValueError("Invalid value for `node_name`, must not be `None`") # noqa: E501 + + self._node_name = node_name + + @property + def type(self): + """Gets the type of this ApplicationInfo. # noqa: E501 + + Application Type # noqa: E501 + + :return: The type of this ApplicationInfo. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this ApplicationInfo. + + Application Type # noqa: E501 + + :param type: The type of this ApplicationInfo. # noqa: E501 + :type: str + """ + allowed_values = ["USER", "SYSTEM"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def persist(self): + """Gets the persist of this ApplicationInfo. # noqa: E501 + + Reserved for internal platform usage # noqa: E501 + + :return: The persist of this ApplicationInfo. # noqa: E501 + :rtype: bool + """ + return self._persist + + @persist.setter + def persist(self, persist): + """Sets the persist of this ApplicationInfo. + + Reserved for internal platform usage # noqa: E501 + + :param persist: The persist of this ApplicationInfo. # noqa: E501 + :type: bool + """ + + self._persist = persist + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ApplicationInfo, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ApplicationInfo): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/cellular_domain_config.py b/examples/demo9/python/mecapp/swagger_client/models/cellular_domain_config.py new file mode 100644 index 0000000000000000000000000000000000000000..af1063450700d65b453e41d994f4c72526d40d28 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/cellular_domain_config.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class CellularDomainConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'mnc': 'str', + 'mcc': 'str', + 'default_cell_id': 'str' + } + + attribute_map = { + 'mnc': 'mnc', + 'mcc': 'mcc', + 'default_cell_id': 'defaultCellId' + } + + def __init__(self, mnc=None, mcc=None, default_cell_id=None): # noqa: E501 + """CellularDomainConfig - a model defined in Swagger""" # noqa: E501 + self._mnc = None + self._mcc = None + self._default_cell_id = None + self.discriminator = None + if mnc is not None: + self.mnc = mnc + if mcc is not None: + self.mcc = mcc + if default_cell_id is not None: + self.default_cell_id = default_cell_id + + @property + def mnc(self): + """Gets the mnc of this CellularDomainConfig. # noqa: E501 + + Mobile Network Code part of PLMN identity as defined in ETSI TS 136 413 # noqa: E501 + + :return: The mnc of this CellularDomainConfig. # noqa: E501 + :rtype: str + """ + return self._mnc + + @mnc.setter + def mnc(self, mnc): + """Sets the mnc of this CellularDomainConfig. + + Mobile Network Code part of PLMN identity as defined in ETSI TS 136 413 # noqa: E501 + + :param mnc: The mnc of this CellularDomainConfig. # noqa: E501 + :type: str + """ + + self._mnc = mnc + + @property + def mcc(self): + """Gets the mcc of this CellularDomainConfig. # noqa: E501 + + Mobile Country Code part of PLMN identity as defined in ETSI TS 136 413 # noqa: E501 + + :return: The mcc of this CellularDomainConfig. # noqa: E501 + :rtype: str + """ + return self._mcc + + @mcc.setter + def mcc(self, mcc): + """Sets the mcc of this CellularDomainConfig. + + Mobile Country Code part of PLMN identity as defined in ETSI TS 136 413 # noqa: E501 + + :param mcc: The mcc of this CellularDomainConfig. # noqa: E501 + :type: str + """ + + self._mcc = mcc + + @property + def default_cell_id(self): + """Gets the default_cell_id of this CellularDomainConfig. # noqa: E501 + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 if no cellId is defined for the cell or if not applicable # noqa: E501 + + :return: The default_cell_id of this CellularDomainConfig. # noqa: E501 + :rtype: str + """ + return self._default_cell_id + + @default_cell_id.setter + def default_cell_id(self, default_cell_id): + """Sets the default_cell_id of this CellularDomainConfig. + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 if no cellId is defined for the cell or if not applicable # noqa: E501 + + :param default_cell_id: The default_cell_id of this CellularDomainConfig. # noqa: E501 + :type: str + """ + + self._default_cell_id = default_cell_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(CellularDomainConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, CellularDomainConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/cellular_poa_config.py b/examples/demo9/python/mecapp/swagger_client/models/cellular_poa_config.py new file mode 100644 index 0000000000000000000000000000000000000000..4477f31d99756602613e435f47c790d2de08f632 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/cellular_poa_config.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class CellularPoaConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'cell_id': 'str' + } + + attribute_map = { + 'cell_id': 'cellId' + } + + def __init__(self, cell_id=None): # noqa: E501 + """CellularPoaConfig - a model defined in Swagger""" # noqa: E501 + self._cell_id = None + self.discriminator = None + if cell_id is not None: + self.cell_id = cell_id + + @property + def cell_id(self): + """Gets the cell_id of this CellularPoaConfig. # noqa: E501 + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell # noqa: E501 + + :return: The cell_id of this CellularPoaConfig. # noqa: E501 + :rtype: str + """ + return self._cell_id + + @cell_id.setter + def cell_id(self, cell_id): + """Sets the cell_id of this CellularPoaConfig. + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell # noqa: E501 + + :param cell_id: The cell_id of this CellularPoaConfig. # noqa: E501 + :type: str + """ + + self._cell_id = cell_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(CellularPoaConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, CellularPoaConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/connectivity_config.py b/examples/demo9/python/mecapp/swagger_client/models/connectivity_config.py new file mode 100644 index 0000000000000000000000000000000000000000..3a27d747574ff54c26d2469fff4f7e9d53d660e6 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/connectivity_config.py @@ -0,0 +1,118 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ConnectivityConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'model': 'str' + } + + attribute_map = { + 'model': 'model' + } + + def __init__(self, model=None): # noqa: E501 + """ConnectivityConfig - a model defined in Swagger""" # noqa: E501 + self._model = None + self.discriminator = None + if model is not None: + self.model = model + + @property + def model(self): + """Gets the model of this ConnectivityConfig. # noqa: E501 + + Connectivity Model:

  • OPEN: Any node in the scenario can communicate with any node
  • PDU: Terminal nodes (UE) require a PDU session to the target DN # noqa: E501 + + :return: The model of this ConnectivityConfig. # noqa: E501 + :rtype: str + """ + return self._model + + @model.setter + def model(self, model): + """Sets the model of this ConnectivityConfig. + + Connectivity Model:
  • OPEN: Any node in the scenario can communicate with any node
  • PDU: Terminal nodes (UE) require a PDU session to the target DN # noqa: E501 + + :param model: The model of this ConnectivityConfig. # noqa: E501 + :type: str + """ + allowed_values = ["OPEN", "PDU"] # noqa: E501 + if model not in allowed_values: + raise ValueError( + "Invalid value for `model` ({0}), must be one of {1}" # noqa: E501 + .format(model, allowed_values) + ) + + self._model = model + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ConnectivityConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ConnectivityConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/cpu_config.py b/examples/demo9/python/mecapp/swagger_client/models/cpu_config.py new file mode 100644 index 0000000000000000000000000000000000000000..ee696b19f6630600df97da01117ffd780a964092 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/cpu_config.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class CpuConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'min': 'float', + 'max': 'float' + } + + attribute_map = { + 'min': 'min', + 'max': 'max' + } + + def __init__(self, min=None, max=None): # noqa: E501 + """CpuConfig - a model defined in Swagger""" # noqa: E501 + self._min = None + self._max = None + self.discriminator = None + if min is not None: + self.min = min + if max is not None: + self.max = max + + @property + def min(self): + """Gets the min of this CpuConfig. # noqa: E501 + + Minimum requested CPU # noqa: E501 + + :return: The min of this CpuConfig. # noqa: E501 + :rtype: float + """ + return self._min + + @min.setter + def min(self, min): + """Sets the min of this CpuConfig. + + Minimum requested CPU # noqa: E501 + + :param min: The min of this CpuConfig. # noqa: E501 + :type: float + """ + + self._min = min + + @property + def max(self): + """Gets the max of this CpuConfig. # noqa: E501 + + Maximum requested CPU # noqa: E501 + + :return: The max of this CpuConfig. # noqa: E501 + :rtype: float + """ + return self._max + + @max.setter + def max(self, max): + """Sets the max of this CpuConfig. + + Maximum requested CPU # noqa: E501 + + :param max: The max of this CpuConfig. # noqa: E501 + :type: float + """ + + self._max = max + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(CpuConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, CpuConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/d2d_config.py b/examples/demo9/python/mecapp/swagger_client/models/d2d_config.py new file mode 100644 index 0000000000000000000000000000000000000000..628d03bcc2351f402ca085c5efc27ddd2fb0d553 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/d2d_config.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class D2dConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'd2d_max_distance': 'float', + 'disable_d2d_via_network': 'bool' + } + + attribute_map = { + 'd2d_max_distance': 'd2dMaxDistance', + 'disable_d2d_via_network': 'disableD2dViaNetwork' + } + + def __init__(self, d2d_max_distance=None, disable_d2d_via_network=None): # noqa: E501 + """D2dConfig - a model defined in Swagger""" # noqa: E501 + self._d2d_max_distance = None + self._disable_d2d_via_network = None + self.discriminator = None + if d2d_max_distance is not None: + self.d2d_max_distance = d2d_max_distance + if disable_d2d_via_network is not None: + self.disable_d2d_via_network = disable_d2d_via_network + + @property + def d2d_max_distance(self): + """Gets the d2d_max_distance of this D2dConfig. # noqa: E501 + + Maximum distance for D2D. Default distance is 100m # noqa: E501 + + :return: The d2d_max_distance of this D2dConfig. # noqa: E501 + :rtype: float + """ + return self._d2d_max_distance + + @d2d_max_distance.setter + def d2d_max_distance(self, d2d_max_distance): + """Sets the d2d_max_distance of this D2dConfig. + + Maximum distance for D2D. Default distance is 100m # noqa: E501 + + :param d2d_max_distance: The d2d_max_distance of this D2dConfig. # noqa: E501 + :type: float + """ + + self._d2d_max_distance = d2d_max_distance + + @property + def disable_d2d_via_network(self): + """Gets the disable_d2d_via_network of this D2dConfig. # noqa: E501 + + Enable-Disable D2D via network. Default value is false # noqa: E501 + + :return: The disable_d2d_via_network of this D2dConfig. # noqa: E501 + :rtype: bool + """ + return self._disable_d2d_via_network + + @disable_d2d_via_network.setter + def disable_d2d_via_network(self, disable_d2d_via_network): + """Sets the disable_d2d_via_network of this D2dConfig. + + Enable-Disable D2D via network. Default value is false # noqa: E501 + + :param disable_d2d_via_network: The disable_d2d_via_network of this D2dConfig. # noqa: E501 + :type: bool + """ + + self._disable_d2d_via_network = disable_d2d_via_network + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(D2dConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, D2dConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/deployment.py b/examples/demo9/python/mecapp/swagger_client/models/deployment.py new file mode 100644 index 0000000000000000000000000000000000000000..61aa2911c4d28243b628e41066c14d185eaf4abd --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/deployment.py @@ -0,0 +1,356 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Deployment(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'net_char': 'NetworkCharacteristics', + 'connectivity': 'ConnectivityConfig', + 'd2d': 'D2dConfig', + 'inter_domain_latency': 'int', + 'inter_domain_latency_variation': 'int', + 'inter_domain_throughput': 'int', + 'inter_domain_packet_loss': 'float', + 'meta': 'dict(str, str)', + 'user_meta': 'dict(str, str)', + 'domains': 'list[Domain]' + } + + attribute_map = { + 'net_char': 'netChar', + 'connectivity': 'connectivity', + 'd2d': 'd2d', + 'inter_domain_latency': 'interDomainLatency', + 'inter_domain_latency_variation': 'interDomainLatencyVariation', + 'inter_domain_throughput': 'interDomainThroughput', + 'inter_domain_packet_loss': 'interDomainPacketLoss', + 'meta': 'meta', + 'user_meta': 'userMeta', + 'domains': 'domains' + } + + def __init__(self, net_char=None, connectivity=None, d2d=None, inter_domain_latency=None, inter_domain_latency_variation=None, inter_domain_throughput=None, inter_domain_packet_loss=None, meta=None, user_meta=None, domains=None): # noqa: E501 + """Deployment - a model defined in Swagger""" # noqa: E501 + self._net_char = None + self._connectivity = None + self._d2d = None + self._inter_domain_latency = None + self._inter_domain_latency_variation = None + self._inter_domain_throughput = None + self._inter_domain_packet_loss = None + self._meta = None + self._user_meta = None + self._domains = None + self.discriminator = None + if net_char is not None: + self.net_char = net_char + if connectivity is not None: + self.connectivity = connectivity + if d2d is not None: + self.d2d = d2d + if inter_domain_latency is not None: + self.inter_domain_latency = inter_domain_latency + if inter_domain_latency_variation is not None: + self.inter_domain_latency_variation = inter_domain_latency_variation + if inter_domain_throughput is not None: + self.inter_domain_throughput = inter_domain_throughput + if inter_domain_packet_loss is not None: + self.inter_domain_packet_loss = inter_domain_packet_loss + if meta is not None: + self.meta = meta + if user_meta is not None: + self.user_meta = user_meta + if domains is not None: + self.domains = domains + + @property + def net_char(self): + """Gets the net_char of this Deployment. # noqa: E501 + + + :return: The net_char of this Deployment. # noqa: E501 + :rtype: NetworkCharacteristics + """ + return self._net_char + + @net_char.setter + def net_char(self, net_char): + """Sets the net_char of this Deployment. + + + :param net_char: The net_char of this Deployment. # noqa: E501 + :type: NetworkCharacteristics + """ + + self._net_char = net_char + + @property + def connectivity(self): + """Gets the connectivity of this Deployment. # noqa: E501 + + + :return: The connectivity of this Deployment. # noqa: E501 + :rtype: ConnectivityConfig + """ + return self._connectivity + + @connectivity.setter + def connectivity(self, connectivity): + """Sets the connectivity of this Deployment. + + + :param connectivity: The connectivity of this Deployment. # noqa: E501 + :type: ConnectivityConfig + """ + + self._connectivity = connectivity + + @property + def d2d(self): + """Gets the d2d of this Deployment. # noqa: E501 + + + :return: The d2d of this Deployment. # noqa: E501 + :rtype: D2dConfig + """ + return self._d2d + + @d2d.setter + def d2d(self, d2d): + """Sets the d2d of this Deployment. + + + :param d2d: The d2d of this Deployment. # noqa: E501 + :type: D2dConfig + """ + + self._d2d = d2d + + @property + def inter_domain_latency(self): + """Gets the inter_domain_latency of this Deployment. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :return: The inter_domain_latency of this Deployment. # noqa: E501 + :rtype: int + """ + return self._inter_domain_latency + + @inter_domain_latency.setter + def inter_domain_latency(self, inter_domain_latency): + """Sets the inter_domain_latency of this Deployment. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :param inter_domain_latency: The inter_domain_latency of this Deployment. # noqa: E501 + :type: int + """ + + self._inter_domain_latency = inter_domain_latency + + @property + def inter_domain_latency_variation(self): + """Gets the inter_domain_latency_variation of this Deployment. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :return: The inter_domain_latency_variation of this Deployment. # noqa: E501 + :rtype: int + """ + return self._inter_domain_latency_variation + + @inter_domain_latency_variation.setter + def inter_domain_latency_variation(self, inter_domain_latency_variation): + """Sets the inter_domain_latency_variation of this Deployment. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :param inter_domain_latency_variation: The inter_domain_latency_variation of this Deployment. # noqa: E501 + :type: int + """ + + self._inter_domain_latency_variation = inter_domain_latency_variation + + @property + def inter_domain_throughput(self): + """Gets the inter_domain_throughput of this Deployment. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :return: The inter_domain_throughput of this Deployment. # noqa: E501 + :rtype: int + """ + return self._inter_domain_throughput + + @inter_domain_throughput.setter + def inter_domain_throughput(self, inter_domain_throughput): + """Sets the inter_domain_throughput of this Deployment. + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :param inter_domain_throughput: The inter_domain_throughput of this Deployment. # noqa: E501 + :type: int + """ + + self._inter_domain_throughput = inter_domain_throughput + + @property + def inter_domain_packet_loss(self): + """Gets the inter_domain_packet_loss of this Deployment. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :return: The inter_domain_packet_loss of this Deployment. # noqa: E501 + :rtype: float + """ + return self._inter_domain_packet_loss + + @inter_domain_packet_loss.setter + def inter_domain_packet_loss(self, inter_domain_packet_loss): + """Sets the inter_domain_packet_loss of this Deployment. + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :param inter_domain_packet_loss: The inter_domain_packet_loss of this Deployment. # noqa: E501 + :type: float + """ + + self._inter_domain_packet_loss = inter_domain_packet_loss + + @property + def meta(self): + """Gets the meta of this Deployment. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The meta of this Deployment. # noqa: E501 + :rtype: dict(str, str) + """ + return self._meta + + @meta.setter + def meta(self, meta): + """Sets the meta of this Deployment. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param meta: The meta of this Deployment. # noqa: E501 + :type: dict(str, str) + """ + + self._meta = meta + + @property + def user_meta(self): + """Gets the user_meta of this Deployment. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The user_meta of this Deployment. # noqa: E501 + :rtype: dict(str, str) + """ + return self._user_meta + + @user_meta.setter + def user_meta(self, user_meta): + """Sets the user_meta of this Deployment. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param user_meta: The user_meta of this Deployment. # noqa: E501 + :type: dict(str, str) + """ + + self._user_meta = user_meta + + @property + def domains(self): + """Gets the domains of this Deployment. # noqa: E501 + + + :return: The domains of this Deployment. # noqa: E501 + :rtype: list[Domain] + """ + return self._domains + + @domains.setter + def domains(self, domains): + """Sets the domains of this Deployment. + + + :param domains: The domains of this Deployment. # noqa: E501 + :type: list[Domain] + """ + + self._domains = domains + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Deployment, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Deployment): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/dn_config.py b/examples/demo9/python/mecapp/swagger_client/models/dn_config.py new file mode 100644 index 0000000000000000000000000000000000000000..7ea5e765cff8cd33a9ae260e60017cb916d5647b --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/dn_config.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class DNConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'dnn': 'str', + 'ladn': 'bool', + 'ecsp': 'str' + } + + attribute_map = { + 'dnn': 'dnn', + 'ladn': 'ladn', + 'ecsp': 'ecsp' + } + + def __init__(self, dnn=None, ladn=None, ecsp=None): # noqa: E501 + """DNConfig - a model defined in Swagger""" # noqa: E501 + self._dnn = None + self._ladn = None + self._ecsp = None + self.discriminator = None + if dnn is not None: + self.dnn = dnn + if ladn is not None: + self.ladn = ladn + if ecsp is not None: + self.ecsp = ecsp + + @property + def dnn(self): + """Gets the dnn of this DNConfig. # noqa: E501 + + Data Network Name # noqa: E501 + + :return: The dnn of this DNConfig. # noqa: E501 + :rtype: str + """ + return self._dnn + + @dnn.setter + def dnn(self, dnn): + """Sets the dnn of this DNConfig. + + Data Network Name # noqa: E501 + + :param dnn: The dnn of this DNConfig. # noqa: E501 + :type: str + """ + + self._dnn = dnn + + @property + def ladn(self): + """Gets the ladn of this DNConfig. # noqa: E501 + + true: Data network serves local area only false: Data network is not limited to local area # noqa: E501 + + :return: The ladn of this DNConfig. # noqa: E501 + :rtype: bool + """ + return self._ladn + + @ladn.setter + def ladn(self, ladn): + """Sets the ladn of this DNConfig. + + true: Data network serves local area only false: Data network is not limited to local area # noqa: E501 + + :param ladn: The ladn of this DNConfig. # noqa: E501 + :type: bool + """ + + self._ladn = ladn + + @property + def ecsp(self): + """Gets the ecsp of this DNConfig. # noqa: E501 + + Edge Compute Service Provider # noqa: E501 + + :return: The ecsp of this DNConfig. # noqa: E501 + :rtype: str + """ + return self._ecsp + + @ecsp.setter + def ecsp(self, ecsp): + """Sets the ecsp of this DNConfig. + + Edge Compute Service Provider # noqa: E501 + + :param ecsp: The ecsp of this DNConfig. # noqa: E501 + :type: str + """ + + self._ecsp = ecsp + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(DNConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, DNConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/domain.py b/examples/demo9/python/mecapp/swagger_client/models/domain.py new file mode 100644 index 0000000000000000000000000000000000000000..19d95e04d37dacc306a70f2cc19af5d48ffedcee --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/domain.py @@ -0,0 +1,420 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Domain(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'name': 'str', + 'type': 'str', + 'net_char': 'NetworkCharacteristics', + 'inter_zone_latency': 'int', + 'inter_zone_latency_variation': 'int', + 'inter_zone_throughput': 'int', + 'inter_zone_packet_loss': 'float', + 'meta': 'dict(str, str)', + 'user_meta': 'dict(str, str)', + 'cellular_domain_config': 'CellularDomainConfig', + 'zones': 'list[Zone]' + } + + attribute_map = { + 'id': 'id', + 'name': 'name', + 'type': 'type', + 'net_char': 'netChar', + 'inter_zone_latency': 'interZoneLatency', + 'inter_zone_latency_variation': 'interZoneLatencyVariation', + 'inter_zone_throughput': 'interZoneThroughput', + 'inter_zone_packet_loss': 'interZonePacketLoss', + 'meta': 'meta', + 'user_meta': 'userMeta', + 'cellular_domain_config': 'cellularDomainConfig', + 'zones': 'zones' + } + + def __init__(self, id=None, name=None, type=None, net_char=None, inter_zone_latency=None, inter_zone_latency_variation=None, inter_zone_throughput=None, inter_zone_packet_loss=None, meta=None, user_meta=None, cellular_domain_config=None, zones=None): # noqa: E501 + """Domain - a model defined in Swagger""" # noqa: E501 + self._id = None + self._name = None + self._type = None + self._net_char = None + self._inter_zone_latency = None + self._inter_zone_latency_variation = None + self._inter_zone_throughput = None + self._inter_zone_packet_loss = None + self._meta = None + self._user_meta = None + self._cellular_domain_config = None + self._zones = None + self.discriminator = None + if id is not None: + self.id = id + if name is not None: + self.name = name + if type is not None: + self.type = type + if net_char is not None: + self.net_char = net_char + if inter_zone_latency is not None: + self.inter_zone_latency = inter_zone_latency + if inter_zone_latency_variation is not None: + self.inter_zone_latency_variation = inter_zone_latency_variation + if inter_zone_throughput is not None: + self.inter_zone_throughput = inter_zone_throughput + if inter_zone_packet_loss is not None: + self.inter_zone_packet_loss = inter_zone_packet_loss + if meta is not None: + self.meta = meta + if user_meta is not None: + self.user_meta = user_meta + if cellular_domain_config is not None: + self.cellular_domain_config = cellular_domain_config + if zones is not None: + self.zones = zones + + @property + def id(self): + """Gets the id of this Domain. # noqa: E501 + + Unique domain ID # noqa: E501 + + :return: The id of this Domain. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Domain. + + Unique domain ID # noqa: E501 + + :param id: The id of this Domain. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this Domain. # noqa: E501 + + Domain name # noqa: E501 + + :return: The name of this Domain. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Domain. + + Domain name # noqa: E501 + + :param name: The name of this Domain. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def type(self): + """Gets the type of this Domain. # noqa: E501 + + Domain type # noqa: E501 + + :return: The type of this Domain. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this Domain. + + Domain type # noqa: E501 + + :param type: The type of this Domain. # noqa: E501 + :type: str + """ + allowed_values = ["OPERATOR", "OPERATOR-CELLULAR", "PUBLIC"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def net_char(self): + """Gets the net_char of this Domain. # noqa: E501 + + + :return: The net_char of this Domain. # noqa: E501 + :rtype: NetworkCharacteristics + """ + return self._net_char + + @net_char.setter + def net_char(self, net_char): + """Sets the net_char of this Domain. + + + :param net_char: The net_char of this Domain. # noqa: E501 + :type: NetworkCharacteristics + """ + + self._net_char = net_char + + @property + def inter_zone_latency(self): + """Gets the inter_zone_latency of this Domain. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :return: The inter_zone_latency of this Domain. # noqa: E501 + :rtype: int + """ + return self._inter_zone_latency + + @inter_zone_latency.setter + def inter_zone_latency(self, inter_zone_latency): + """Sets the inter_zone_latency of this Domain. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :param inter_zone_latency: The inter_zone_latency of this Domain. # noqa: E501 + :type: int + """ + + self._inter_zone_latency = inter_zone_latency + + @property + def inter_zone_latency_variation(self): + """Gets the inter_zone_latency_variation of this Domain. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :return: The inter_zone_latency_variation of this Domain. # noqa: E501 + :rtype: int + """ + return self._inter_zone_latency_variation + + @inter_zone_latency_variation.setter + def inter_zone_latency_variation(self, inter_zone_latency_variation): + """Sets the inter_zone_latency_variation of this Domain. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :param inter_zone_latency_variation: The inter_zone_latency_variation of this Domain. # noqa: E501 + :type: int + """ + + self._inter_zone_latency_variation = inter_zone_latency_variation + + @property + def inter_zone_throughput(self): + """Gets the inter_zone_throughput of this Domain. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :return: The inter_zone_throughput of this Domain. # noqa: E501 + :rtype: int + """ + return self._inter_zone_throughput + + @inter_zone_throughput.setter + def inter_zone_throughput(self, inter_zone_throughput): + """Sets the inter_zone_throughput of this Domain. + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :param inter_zone_throughput: The inter_zone_throughput of this Domain. # noqa: E501 + :type: int + """ + + self._inter_zone_throughput = inter_zone_throughput + + @property + def inter_zone_packet_loss(self): + """Gets the inter_zone_packet_loss of this Domain. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :return: The inter_zone_packet_loss of this Domain. # noqa: E501 + :rtype: float + """ + return self._inter_zone_packet_loss + + @inter_zone_packet_loss.setter + def inter_zone_packet_loss(self, inter_zone_packet_loss): + """Sets the inter_zone_packet_loss of this Domain. + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :param inter_zone_packet_loss: The inter_zone_packet_loss of this Domain. # noqa: E501 + :type: float + """ + + self._inter_zone_packet_loss = inter_zone_packet_loss + + @property + def meta(self): + """Gets the meta of this Domain. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The meta of this Domain. # noqa: E501 + :rtype: dict(str, str) + """ + return self._meta + + @meta.setter + def meta(self, meta): + """Sets the meta of this Domain. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param meta: The meta of this Domain. # noqa: E501 + :type: dict(str, str) + """ + + self._meta = meta + + @property + def user_meta(self): + """Gets the user_meta of this Domain. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The user_meta of this Domain. # noqa: E501 + :rtype: dict(str, str) + """ + return self._user_meta + + @user_meta.setter + def user_meta(self, user_meta): + """Sets the user_meta of this Domain. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param user_meta: The user_meta of this Domain. # noqa: E501 + :type: dict(str, str) + """ + + self._user_meta = user_meta + + @property + def cellular_domain_config(self): + """Gets the cellular_domain_config of this Domain. # noqa: E501 + + + :return: The cellular_domain_config of this Domain. # noqa: E501 + :rtype: CellularDomainConfig + """ + return self._cellular_domain_config + + @cellular_domain_config.setter + def cellular_domain_config(self, cellular_domain_config): + """Sets the cellular_domain_config of this Domain. + + + :param cellular_domain_config: The cellular_domain_config of this Domain. # noqa: E501 + :type: CellularDomainConfig + """ + + self._cellular_domain_config = cellular_domain_config + + @property + def zones(self): + """Gets the zones of this Domain. # noqa: E501 + + + :return: The zones of this Domain. # noqa: E501 + :rtype: list[Zone] + """ + return self._zones + + @zones.setter + def zones(self, zones): + """Sets the zones of this Domain. + + + :param zones: The zones of this Domain. # noqa: E501 + :type: list[Zone] + """ + + self._zones = zones + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Domain, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Domain): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/egress_service.py b/examples/demo9/python/mecapp/swagger_client/models/egress_service.py new file mode 100644 index 0000000000000000000000000000000000000000..deb3ce4d6880b3cc7085b291b11bb5b3240ea4c9 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/egress_service.py @@ -0,0 +1,224 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class EgressService(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'name': 'str', + 'me_svc_name': 'str', + 'ip': 'str', + 'port': 'int', + 'protocol': 'str' + } + + attribute_map = { + 'name': 'name', + 'me_svc_name': 'meSvcName', + 'ip': 'ip', + 'port': 'port', + 'protocol': 'protocol' + } + + def __init__(self, name=None, me_svc_name=None, ip=None, port=None, protocol=None): # noqa: E501 + """EgressService - a model defined in Swagger""" # noqa: E501 + self._name = None + self._me_svc_name = None + self._ip = None + self._port = None + self._protocol = None + self.discriminator = None + if name is not None: + self.name = name + if me_svc_name is not None: + self.me_svc_name = me_svc_name + if ip is not None: + self.ip = ip + if port is not None: + self.port = port + if protocol is not None: + self.protocol = protocol + + @property + def name(self): + """Gets the name of this EgressService. # noqa: E501 + + Service name # noqa: E501 + + :return: The name of this EgressService. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this EgressService. + + Service name # noqa: E501 + + :param name: The name of this EgressService. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def me_svc_name(self): + """Gets the me_svc_name of this EgressService. # noqa: E501 + + Multi-Edge service name, if any # noqa: E501 + + :return: The me_svc_name of this EgressService. # noqa: E501 + :rtype: str + """ + return self._me_svc_name + + @me_svc_name.setter + def me_svc_name(self, me_svc_name): + """Sets the me_svc_name of this EgressService. + + Multi-Edge service name, if any # noqa: E501 + + :param me_svc_name: The me_svc_name of this EgressService. # noqa: E501 + :type: str + """ + + self._me_svc_name = me_svc_name + + @property + def ip(self): + """Gets the ip of this EgressService. # noqa: E501 + + External node IP address # noqa: E501 + + :return: The ip of this EgressService. # noqa: E501 + :rtype: str + """ + return self._ip + + @ip.setter + def ip(self, ip): + """Sets the ip of this EgressService. + + External node IP address # noqa: E501 + + :param ip: The ip of this EgressService. # noqa: E501 + :type: str + """ + + self._ip = ip + + @property + def port(self): + """Gets the port of this EgressService. # noqa: E501 + + Service port number # noqa: E501 + + :return: The port of this EgressService. # noqa: E501 + :rtype: int + """ + return self._port + + @port.setter + def port(self, port): + """Sets the port of this EgressService. + + Service port number # noqa: E501 + + :param port: The port of this EgressService. # noqa: E501 + :type: int + """ + + self._port = port + + @property + def protocol(self): + """Gets the protocol of this EgressService. # noqa: E501 + + Service protocol (TCP or UDP) # noqa: E501 + + :return: The protocol of this EgressService. # noqa: E501 + :rtype: str + """ + return self._protocol + + @protocol.setter + def protocol(self, protocol): + """Sets the protocol of this EgressService. + + Service protocol (TCP or UDP) # noqa: E501 + + :param protocol: The protocol of this EgressService. # noqa: E501 + :type: str + """ + + self._protocol = protocol + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(EgressService, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, EgressService): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/external_config.py b/examples/demo9/python/mecapp/swagger_client/models/external_config.py new file mode 100644 index 0000000000000000000000000000000000000000..4450e3928f8d38a23fb7a158e55608300eae02dd --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/external_config.py @@ -0,0 +1,136 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ExternalConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'ingress_service_map': 'list[IngressService]', + 'egress_service_map': 'list[EgressService]' + } + + attribute_map = { + 'ingress_service_map': 'ingressServiceMap', + 'egress_service_map': 'egressServiceMap' + } + + def __init__(self, ingress_service_map=None, egress_service_map=None): # noqa: E501 + """ExternalConfig - a model defined in Swagger""" # noqa: E501 + self._ingress_service_map = None + self._egress_service_map = None + self.discriminator = None + if ingress_service_map is not None: + self.ingress_service_map = ingress_service_map + if egress_service_map is not None: + self.egress_service_map = egress_service_map + + @property + def ingress_service_map(self): + """Gets the ingress_service_map of this ExternalConfig. # noqa: E501 + + + :return: The ingress_service_map of this ExternalConfig. # noqa: E501 + :rtype: list[IngressService] + """ + return self._ingress_service_map + + @ingress_service_map.setter + def ingress_service_map(self, ingress_service_map): + """Sets the ingress_service_map of this ExternalConfig. + + + :param ingress_service_map: The ingress_service_map of this ExternalConfig. # noqa: E501 + :type: list[IngressService] + """ + + self._ingress_service_map = ingress_service_map + + @property + def egress_service_map(self): + """Gets the egress_service_map of this ExternalConfig. # noqa: E501 + + + :return: The egress_service_map of this ExternalConfig. # noqa: E501 + :rtype: list[EgressService] + """ + return self._egress_service_map + + @egress_service_map.setter + def egress_service_map(self, egress_service_map): + """Sets the egress_service_map of this ExternalConfig. + + + :param egress_service_map: The egress_service_map of this ExternalConfig. # noqa: E501 + :type: list[EgressService] + """ + + self._egress_service_map = egress_service_map + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ExternalConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ExternalConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/geo_data.py b/examples/demo9/python/mecapp/swagger_client/models/geo_data.py new file mode 100644 index 0000000000000000000000000000000000000000..3497469eb354bcde46a6f5f4a59eec4aaf3de4dc --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/geo_data.py @@ -0,0 +1,278 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class GeoData(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'location': 'Point', + 'radius': 'float', + 'path': 'LineString', + 'eop_mode': 'str', + 'velocity': 'float', + 'd2d_in_range': 'list[str]', + 'poa_in_range': 'list[str]' + } + + attribute_map = { + 'location': 'location', + 'radius': 'radius', + 'path': 'path', + 'eop_mode': 'eopMode', + 'velocity': 'velocity', + 'd2d_in_range': 'd2dInRange', + 'poa_in_range': 'poaInRange' + } + + def __init__(self, location=None, radius=None, path=None, eop_mode=None, velocity=None, d2d_in_range=None, poa_in_range=None): # noqa: E501 + """GeoData - a model defined in Swagger""" # noqa: E501 + self._location = None + self._radius = None + self._path = None + self._eop_mode = None + self._velocity = None + self._d2d_in_range = None + self._poa_in_range = None + self.discriminator = None + if location is not None: + self.location = location + if radius is not None: + self.radius = radius + if path is not None: + self.path = path + if eop_mode is not None: + self.eop_mode = eop_mode + if velocity is not None: + self.velocity = velocity + if d2d_in_range is not None: + self.d2d_in_range = d2d_in_range + if poa_in_range is not None: + self.poa_in_range = poa_in_range + + @property + def location(self): + """Gets the location of this GeoData. # noqa: E501 + + + :return: The location of this GeoData. # noqa: E501 + :rtype: Point + """ + return self._location + + @location.setter + def location(self, location): + """Sets the location of this GeoData. + + + :param location: The location of this GeoData. # noqa: E501 + :type: Point + """ + + self._location = location + + @property + def radius(self): + """Gets the radius of this GeoData. # noqa: E501 + + Optional - Radius (in meters) around the location # noqa: E501 + + :return: The radius of this GeoData. # noqa: E501 + :rtype: float + """ + return self._radius + + @radius.setter + def radius(self, radius): + """Sets the radius of this GeoData. + + Optional - Radius (in meters) around the location # noqa: E501 + + :param radius: The radius of this GeoData. # noqa: E501 + :type: float + """ + + self._radius = radius + + @property + def path(self): + """Gets the path of this GeoData. # noqa: E501 + + + :return: The path of this GeoData. # noqa: E501 + :rtype: LineString + """ + return self._path + + @path.setter + def path(self, path): + """Sets the path of this GeoData. + + + :param path: The path of this GeoData. # noqa: E501 + :type: LineString + """ + + self._path = path + + @property + def eop_mode(self): + """Gets the eop_mode of this GeoData. # noqa: E501 + + End-of-Path mode:
  • LOOP: When path endpoint is reached, start over from the beginning
  • REVERSE: When path endpoint is reached, return on the reverse path # noqa: E501 + + :return: The eop_mode of this GeoData. # noqa: E501 + :rtype: str + """ + return self._eop_mode + + @eop_mode.setter + def eop_mode(self, eop_mode): + """Sets the eop_mode of this GeoData. + + End-of-Path mode:
  • LOOP: When path endpoint is reached, start over from the beginning
  • REVERSE: When path endpoint is reached, return on the reverse path # noqa: E501 + + :param eop_mode: The eop_mode of this GeoData. # noqa: E501 + :type: str + """ + allowed_values = ["LOOP", "REVERSE"] # noqa: E501 + if eop_mode not in allowed_values: + raise ValueError( + "Invalid value for `eop_mode` ({0}), must be one of {1}" # noqa: E501 + .format(eop_mode, allowed_values) + ) + + self._eop_mode = eop_mode + + @property + def velocity(self): + """Gets the velocity of this GeoData. # noqa: E501 + + Speed of movement along path in m/s # noqa: E501 + + :return: The velocity of this GeoData. # noqa: E501 + :rtype: float + """ + return self._velocity + + @velocity.setter + def velocity(self, velocity): + """Sets the velocity of this GeoData. + + Speed of movement along path in m/s # noqa: E501 + + :param velocity: The velocity of this GeoData. # noqa: E501 + :type: float + """ + + self._velocity = velocity + + @property + def d2d_in_range(self): + """Gets the d2d_in_range of this GeoData. # noqa: E501 + + + :return: The d2d_in_range of this GeoData. # noqa: E501 + :rtype: list[str] + """ + return self._d2d_in_range + + @d2d_in_range.setter + def d2d_in_range(self, d2d_in_range): + """Sets the d2d_in_range of this GeoData. + + + :param d2d_in_range: The d2d_in_range of this GeoData. # noqa: E501 + :type: list[str] + """ + + self._d2d_in_range = d2d_in_range + + @property + def poa_in_range(self): + """Gets the poa_in_range of this GeoData. # noqa: E501 + + + :return: The poa_in_range of this GeoData. # noqa: E501 + :rtype: list[str] + """ + return self._poa_in_range + + @poa_in_range.setter + def poa_in_range(self, poa_in_range): + """Sets the poa_in_range of this GeoData. + + + :param poa_in_range: The poa_in_range of this GeoData. # noqa: E501 + :type: list[str] + """ + + self._poa_in_range = poa_in_range + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(GeoData, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, GeoData): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/gpu_config.py b/examples/demo9/python/mecapp/swagger_client/models/gpu_config.py new file mode 100644 index 0000000000000000000000000000000000000000..89cbdd5cbedb51e9d3ad6c44e420ddc2ce448f0d --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/gpu_config.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class GpuConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'type': 'str', + 'count': 'int' + } + + attribute_map = { + 'type': 'type', + 'count': 'count' + } + + def __init__(self, type=None, count=None): # noqa: E501 + """GpuConfig - a model defined in Swagger""" # noqa: E501 + self._type = None + self._count = None + self.discriminator = None + if type is not None: + self.type = type + if count is not None: + self.count = count + + @property + def type(self): + """Gets the type of this GpuConfig. # noqa: E501 + + Requested GPU type # noqa: E501 + + :return: The type of this GpuConfig. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this GpuConfig. + + Requested GPU type # noqa: E501 + + :param type: The type of this GpuConfig. # noqa: E501 + :type: str + """ + + self._type = type + + @property + def count(self): + """Gets the count of this GpuConfig. # noqa: E501 + + Number of GPUs requested # noqa: E501 + + :return: The count of this GpuConfig. # noqa: E501 + :rtype: int + """ + return self._count + + @count.setter + def count(self, count): + """Sets the count of this GpuConfig. + + Number of GPUs requested # noqa: E501 + + :param count: The count of this GpuConfig. # noqa: E501 + :type: int + """ + + self._count = count + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(GpuConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, GpuConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/ingress_service.py b/examples/demo9/python/mecapp/swagger_client/models/ingress_service.py new file mode 100644 index 0000000000000000000000000000000000000000..c4ae67fa24de2efbd3f18d9710385ae900774240 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/ingress_service.py @@ -0,0 +1,196 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class IngressService(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'name': 'str', + 'port': 'int', + 'external_port': 'int', + 'protocol': 'str' + } + + attribute_map = { + 'name': 'name', + 'port': 'port', + 'external_port': 'externalPort', + 'protocol': 'protocol' + } + + def __init__(self, name=None, port=None, external_port=None, protocol=None): # noqa: E501 + """IngressService - a model defined in Swagger""" # noqa: E501 + self._name = None + self._port = None + self._external_port = None + self._protocol = None + self.discriminator = None + if name is not None: + self.name = name + if port is not None: + self.port = port + if external_port is not None: + self.external_port = external_port + if protocol is not None: + self.protocol = protocol + + @property + def name(self): + """Gets the name of this IngressService. # noqa: E501 + + Service name (unique or multi-edge) # noqa: E501 + + :return: The name of this IngressService. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this IngressService. + + Service name (unique or multi-edge) # noqa: E501 + + :param name: The name of this IngressService. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def port(self): + """Gets the port of this IngressService. # noqa: E501 + + Internal service port number # noqa: E501 + + :return: The port of this IngressService. # noqa: E501 + :rtype: int + """ + return self._port + + @port.setter + def port(self, port): + """Sets the port of this IngressService. + + Internal service port number # noqa: E501 + + :param port: The port of this IngressService. # noqa: E501 + :type: int + """ + + self._port = port + + @property + def external_port(self): + """Gets the external_port of this IngressService. # noqa: E501 + + Externally-exposed unique service port in range (30000 - 32767) # noqa: E501 + + :return: The external_port of this IngressService. # noqa: E501 + :rtype: int + """ + return self._external_port + + @external_port.setter + def external_port(self, external_port): + """Sets the external_port of this IngressService. + + Externally-exposed unique service port in range (30000 - 32767) # noqa: E501 + + :param external_port: The external_port of this IngressService. # noqa: E501 + :type: int + """ + + self._external_port = external_port + + @property + def protocol(self): + """Gets the protocol of this IngressService. # noqa: E501 + + Service protocol (TCP or UDP) # noqa: E501 + + :return: The protocol of this IngressService. # noqa: E501 + :rtype: str + """ + return self._protocol + + @protocol.setter + def protocol(self, protocol): + """Sets the protocol of this IngressService. + + Service protocol (TCP or UDP) # noqa: E501 + + :param protocol: The protocol of this IngressService. # noqa: E501 + :type: str + """ + + self._protocol = protocol + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(IngressService, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, IngressService): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/line_string.py b/examples/demo9/python/mecapp/swagger_client/models/line_string.py new file mode 100644 index 0000000000000000000000000000000000000000..980a97512a452034ad3fa22412385f18b4b8ff71 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/line_string.py @@ -0,0 +1,147 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class LineString(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'type': 'str', + 'coordinates': 'list[list[float]]' + } + + attribute_map = { + 'type': 'type', + 'coordinates': 'coordinates' + } + + def __init__(self, type=None, coordinates=None): # noqa: E501 + """LineString - a model defined in Swagger""" # noqa: E501 + self._type = None + self._coordinates = None + self.discriminator = None + self.type = type + if coordinates is not None: + self.coordinates = coordinates + + @property + def type(self): + """Gets the type of this LineString. # noqa: E501 + + Must be LineString # noqa: E501 + + :return: The type of this LineString. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this LineString. + + Must be LineString # noqa: E501 + + :param type: The type of this LineString. # noqa: E501 + :type: str + """ + if type is None: + raise ValueError("Invalid value for `type`, must not be `None`") # noqa: E501 + allowed_values = ["LineString"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def coordinates(self): + """Gets the coordinates of this LineString. # noqa: E501 + + For a LineString, coordinates is an array of two or more positions; a position is an array of two decimal numbers (longitude and latitude precisely in that order) # noqa: E501 + + :return: The coordinates of this LineString. # noqa: E501 + :rtype: list[list[float]] + """ + return self._coordinates + + @coordinates.setter + def coordinates(self, coordinates): + """Sets the coordinates of this LineString. + + For a LineString, coordinates is an array of two or more positions; a position is an array of two decimal numbers (longitude and latitude precisely in that order) # noqa: E501 + + :param coordinates: The coordinates of this LineString. # noqa: E501 + :type: list[list[float]] + """ + + self._coordinates = coordinates + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(LineString, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, LineString): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/memory_config.py b/examples/demo9/python/mecapp/swagger_client/models/memory_config.py new file mode 100644 index 0000000000000000000000000000000000000000..12f6b1030fe2147501357ca83d4ab12af0281f3c --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/memory_config.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class MemoryConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'min': 'int', + 'max': 'int' + } + + attribute_map = { + 'min': 'min', + 'max': 'max' + } + + def __init__(self, min=None, max=None): # noqa: E501 + """MemoryConfig - a model defined in Swagger""" # noqa: E501 + self._min = None + self._max = None + self.discriminator = None + if min is not None: + self.min = min + if max is not None: + self.max = max + + @property + def min(self): + """Gets the min of this MemoryConfig. # noqa: E501 + + Minimum requested memory # noqa: E501 + + :return: The min of this MemoryConfig. # noqa: E501 + :rtype: int + """ + return self._min + + @min.setter + def min(self, min): + """Sets the min of this MemoryConfig. + + Minimum requested memory # noqa: E501 + + :param min: The min of this MemoryConfig. # noqa: E501 + :type: int + """ + + self._min = min + + @property + def max(self): + """Gets the max of this MemoryConfig. # noqa: E501 + + Maximum requested memory # noqa: E501 + + :return: The max of this MemoryConfig. # noqa: E501 + :rtype: int + """ + return self._max + + @max.setter + def max(self, max): + """Sets the max of this MemoryConfig. + + Maximum requested memory # noqa: E501 + + :param max: The max of this MemoryConfig. # noqa: E501 + :type: int + """ + + self._max = max + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(MemoryConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, MemoryConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/namespace.py b/examples/demo9/python/mecapp/swagger_client/models/namespace.py new file mode 100644 index 0000000000000000000000000000000000000000..567e5c11f28757f6108400da14de321a3f74ebb0 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/namespace.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Namespace(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'sandbox_name': 'str' + } + + attribute_map = { + 'sandbox_name': 'sandbox_name' + } + + def __init__(self, sandbox_name=None): # noqa: E501 + """Namespace - a model defined in Swagger""" # noqa: E501 + self._sandbox_name = None + self.discriminator = None + if sandbox_name is not None: + self.sandbox_name = sandbox_name + + @property + def sandbox_name(self): + """Gets the sandbox_name of this Namespace. # noqa: E501 + + Name of the Sandbox # noqa: E501 + + :return: The sandbox_name of this Namespace. # noqa: E501 + :rtype: str + """ + return self._sandbox_name + + @sandbox_name.setter + def sandbox_name(self, sandbox_name): + """Sets the sandbox_name of this Namespace. + + Name of the Sandbox # noqa: E501 + + :param sandbox_name: The sandbox_name of this Namespace. # noqa: E501 + :type: str + """ + + self._sandbox_name = sandbox_name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Namespace, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Namespace): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/network_characteristics.py b/examples/demo9/python/mecapp/swagger_client/models/network_characteristics.py new file mode 100644 index 0000000000000000000000000000000000000000..52f57271fbaed8687f20ccbdc421c8969eaef92b --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/network_characteristics.py @@ -0,0 +1,286 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class NetworkCharacteristics(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'latency': 'int', + 'latency_variation': 'int', + 'latency_distribution': 'str', + 'throughput': 'int', + 'throughput_dl': 'int', + 'throughput_ul': 'int', + 'packet_loss': 'float' + } + + attribute_map = { + 'latency': 'latency', + 'latency_variation': 'latencyVariation', + 'latency_distribution': 'latencyDistribution', + 'throughput': 'throughput', + 'throughput_dl': 'throughputDl', + 'throughput_ul': 'throughputUl', + 'packet_loss': 'packetLoss' + } + + def __init__(self, latency=None, latency_variation=None, latency_distribution=None, throughput=None, throughput_dl=None, throughput_ul=None, packet_loss=None): # noqa: E501 + """NetworkCharacteristics - a model defined in Swagger""" # noqa: E501 + self._latency = None + self._latency_variation = None + self._latency_distribution = None + self._throughput = None + self._throughput_dl = None + self._throughput_ul = None + self._packet_loss = None + self.discriminator = None + if latency is not None: + self.latency = latency + if latency_variation is not None: + self.latency_variation = latency_variation + if latency_distribution is not None: + self.latency_distribution = latency_distribution + if throughput is not None: + self.throughput = throughput + if throughput_dl is not None: + self.throughput_dl = throughput_dl + if throughput_ul is not None: + self.throughput_ul = throughput_ul + if packet_loss is not None: + self.packet_loss = packet_loss + + @property + def latency(self): + """Gets the latency of this NetworkCharacteristics. # noqa: E501 + + Latency in ms # noqa: E501 + + :return: The latency of this NetworkCharacteristics. # noqa: E501 + :rtype: int + """ + return self._latency + + @latency.setter + def latency(self, latency): + """Sets the latency of this NetworkCharacteristics. + + Latency in ms # noqa: E501 + + :param latency: The latency of this NetworkCharacteristics. # noqa: E501 + :type: int + """ + + self._latency = latency + + @property + def latency_variation(self): + """Gets the latency_variation of this NetworkCharacteristics. # noqa: E501 + + Latency variation in ms # noqa: E501 + + :return: The latency_variation of this NetworkCharacteristics. # noqa: E501 + :rtype: int + """ + return self._latency_variation + + @latency_variation.setter + def latency_variation(self, latency_variation): + """Sets the latency_variation of this NetworkCharacteristics. + + Latency variation in ms # noqa: E501 + + :param latency_variation: The latency_variation of this NetworkCharacteristics. # noqa: E501 + :type: int + """ + + self._latency_variation = latency_variation + + @property + def latency_distribution(self): + """Gets the latency_distribution of this NetworkCharacteristics. # noqa: E501 + + Latency distribution. Can only be set in the Scenario Deployment network characteristics, ignored otherwise. Latency distribution is set for the whole network and applied to every end-to-end traffic flows. Default value is 'Normal' distribution. # noqa: E501 + + :return: The latency_distribution of this NetworkCharacteristics. # noqa: E501 + :rtype: str + """ + return self._latency_distribution + + @latency_distribution.setter + def latency_distribution(self, latency_distribution): + """Sets the latency_distribution of this NetworkCharacteristics. + + Latency distribution. Can only be set in the Scenario Deployment network characteristics, ignored otherwise. Latency distribution is set for the whole network and applied to every end-to-end traffic flows. Default value is 'Normal' distribution. # noqa: E501 + + :param latency_distribution: The latency_distribution of this NetworkCharacteristics. # noqa: E501 + :type: str + """ + allowed_values = ["Normal", "Pareto", "Paretonormal", "Uniform"] # noqa: E501 + if latency_distribution not in allowed_values: + raise ValueError( + "Invalid value for `latency_distribution` ({0}), must be one of {1}" # noqa: E501 + .format(latency_distribution, allowed_values) + ) + + self._latency_distribution = latency_distribution + + @property + def throughput(self): + """Gets the throughput of this NetworkCharacteristics. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by throughputUl and throughputDl # noqa: E501 + + :return: The throughput of this NetworkCharacteristics. # noqa: E501 + :rtype: int + """ + return self._throughput + + @throughput.setter + def throughput(self, throughput): + """Sets the throughput of this NetworkCharacteristics. + + **DEPRECATED** As of release 1.5.0, replaced by throughputUl and throughputDl # noqa: E501 + + :param throughput: The throughput of this NetworkCharacteristics. # noqa: E501 + :type: int + """ + + self._throughput = throughput + + @property + def throughput_dl(self): + """Gets the throughput_dl of this NetworkCharacteristics. # noqa: E501 + + Downlink throughput limit in Mbps # noqa: E501 + + :return: The throughput_dl of this NetworkCharacteristics. # noqa: E501 + :rtype: int + """ + return self._throughput_dl + + @throughput_dl.setter + def throughput_dl(self, throughput_dl): + """Sets the throughput_dl of this NetworkCharacteristics. + + Downlink throughput limit in Mbps # noqa: E501 + + :param throughput_dl: The throughput_dl of this NetworkCharacteristics. # noqa: E501 + :type: int + """ + + self._throughput_dl = throughput_dl + + @property + def throughput_ul(self): + """Gets the throughput_ul of this NetworkCharacteristics. # noqa: E501 + + Uplink throughput limit in Mbps # noqa: E501 + + :return: The throughput_ul of this NetworkCharacteristics. # noqa: E501 + :rtype: int + """ + return self._throughput_ul + + @throughput_ul.setter + def throughput_ul(self, throughput_ul): + """Sets the throughput_ul of this NetworkCharacteristics. + + Uplink throughput limit in Mbps # noqa: E501 + + :param throughput_ul: The throughput_ul of this NetworkCharacteristics. # noqa: E501 + :type: int + """ + + self._throughput_ul = throughput_ul + + @property + def packet_loss(self): + """Gets the packet_loss of this NetworkCharacteristics. # noqa: E501 + + Packet loss percentage # noqa: E501 + + :return: The packet_loss of this NetworkCharacteristics. # noqa: E501 + :rtype: float + """ + return self._packet_loss + + @packet_loss.setter + def packet_loss(self, packet_loss): + """Sets the packet_loss of this NetworkCharacteristics. + + Packet loss percentage # noqa: E501 + + :param packet_loss: The packet_loss of this NetworkCharacteristics. # noqa: E501 + :type: float + """ + + self._packet_loss = packet_loss + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(NetworkCharacteristics, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, NetworkCharacteristics): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/network_location.py b/examples/demo9/python/mecapp/swagger_client/models/network_location.py new file mode 100644 index 0000000000000000000000000000000000000000..0fc0a0038b9049f44a379c18895196af622aa1a1 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/network_location.py @@ -0,0 +1,524 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class NetworkLocation(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'name': 'str', + 'type': 'str', + 'net_char': 'NetworkCharacteristics', + 'terminal_link_latency': 'int', + 'terminal_link_latency_variation': 'int', + 'terminal_link_throughput': 'int', + 'terminal_link_packet_loss': 'float', + 'meta': 'dict(str, str)', + 'user_meta': 'dict(str, str)', + 'cellular_poa_config': 'CellularPoaConfig', + 'poa4_g_config': 'Poa4GConfig', + 'poa5_g_config': 'Poa5GConfig', + 'poa_wifi_config': 'PoaWifiConfig', + 'geo_data': 'GeoData', + 'physical_locations': 'list[PhysicalLocation]' + } + + attribute_map = { + 'id': 'id', + 'name': 'name', + 'type': 'type', + 'net_char': 'netChar', + 'terminal_link_latency': 'terminalLinkLatency', + 'terminal_link_latency_variation': 'terminalLinkLatencyVariation', + 'terminal_link_throughput': 'terminalLinkThroughput', + 'terminal_link_packet_loss': 'terminalLinkPacketLoss', + 'meta': 'meta', + 'user_meta': 'userMeta', + 'cellular_poa_config': 'cellularPoaConfig', + 'poa4_g_config': 'poa4GConfig', + 'poa5_g_config': 'poa5GConfig', + 'poa_wifi_config': 'poaWifiConfig', + 'geo_data': 'geoData', + 'physical_locations': 'physicalLocations' + } + + def __init__(self, id=None, name=None, type=None, net_char=None, terminal_link_latency=None, terminal_link_latency_variation=None, terminal_link_throughput=None, terminal_link_packet_loss=None, meta=None, user_meta=None, cellular_poa_config=None, poa4_g_config=None, poa5_g_config=None, poa_wifi_config=None, geo_data=None, physical_locations=None): # noqa: E501 + """NetworkLocation - a model defined in Swagger""" # noqa: E501 + self._id = None + self._name = None + self._type = None + self._net_char = None + self._terminal_link_latency = None + self._terminal_link_latency_variation = None + self._terminal_link_throughput = None + self._terminal_link_packet_loss = None + self._meta = None + self._user_meta = None + self._cellular_poa_config = None + self._poa4_g_config = None + self._poa5_g_config = None + self._poa_wifi_config = None + self._geo_data = None + self._physical_locations = None + self.discriminator = None + if id is not None: + self.id = id + if name is not None: + self.name = name + if type is not None: + self.type = type + if net_char is not None: + self.net_char = net_char + if terminal_link_latency is not None: + self.terminal_link_latency = terminal_link_latency + if terminal_link_latency_variation is not None: + self.terminal_link_latency_variation = terminal_link_latency_variation + if terminal_link_throughput is not None: + self.terminal_link_throughput = terminal_link_throughput + if terminal_link_packet_loss is not None: + self.terminal_link_packet_loss = terminal_link_packet_loss + if meta is not None: + self.meta = meta + if user_meta is not None: + self.user_meta = user_meta + if cellular_poa_config is not None: + self.cellular_poa_config = cellular_poa_config + if poa4_g_config is not None: + self.poa4_g_config = poa4_g_config + if poa5_g_config is not None: + self.poa5_g_config = poa5_g_config + if poa_wifi_config is not None: + self.poa_wifi_config = poa_wifi_config + if geo_data is not None: + self.geo_data = geo_data + if physical_locations is not None: + self.physical_locations = physical_locations + + @property + def id(self): + """Gets the id of this NetworkLocation. # noqa: E501 + + Unique network location ID # noqa: E501 + + :return: The id of this NetworkLocation. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this NetworkLocation. + + Unique network location ID # noqa: E501 + + :param id: The id of this NetworkLocation. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this NetworkLocation. # noqa: E501 + + Network location name # noqa: E501 + + :return: The name of this NetworkLocation. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this NetworkLocation. + + Network location name # noqa: E501 + + :param name: The name of this NetworkLocation. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def type(self): + """Gets the type of this NetworkLocation. # noqa: E501 + + Network location type # noqa: E501 + + :return: The type of this NetworkLocation. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this NetworkLocation. + + Network location type # noqa: E501 + + :param type: The type of this NetworkLocation. # noqa: E501 + :type: str + """ + allowed_values = ["POA", "POA-4G", "POA-5G", "POA-WIFI", "DEFAULT"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def net_char(self): + """Gets the net_char of this NetworkLocation. # noqa: E501 + + + :return: The net_char of this NetworkLocation. # noqa: E501 + :rtype: NetworkCharacteristics + """ + return self._net_char + + @net_char.setter + def net_char(self, net_char): + """Sets the net_char of this NetworkLocation. + + + :param net_char: The net_char of this NetworkLocation. # noqa: E501 + :type: NetworkCharacteristics + """ + + self._net_char = net_char + + @property + def terminal_link_latency(self): + """Gets the terminal_link_latency of this NetworkLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :return: The terminal_link_latency of this NetworkLocation. # noqa: E501 + :rtype: int + """ + return self._terminal_link_latency + + @terminal_link_latency.setter + def terminal_link_latency(self, terminal_link_latency): + """Sets the terminal_link_latency of this NetworkLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :param terminal_link_latency: The terminal_link_latency of this NetworkLocation. # noqa: E501 + :type: int + """ + + self._terminal_link_latency = terminal_link_latency + + @property + def terminal_link_latency_variation(self): + """Gets the terminal_link_latency_variation of this NetworkLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :return: The terminal_link_latency_variation of this NetworkLocation. # noqa: E501 + :rtype: int + """ + return self._terminal_link_latency_variation + + @terminal_link_latency_variation.setter + def terminal_link_latency_variation(self, terminal_link_latency_variation): + """Sets the terminal_link_latency_variation of this NetworkLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :param terminal_link_latency_variation: The terminal_link_latency_variation of this NetworkLocation. # noqa: E501 + :type: int + """ + + self._terminal_link_latency_variation = terminal_link_latency_variation + + @property + def terminal_link_throughput(self): + """Gets the terminal_link_throughput of this NetworkLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :return: The terminal_link_throughput of this NetworkLocation. # noqa: E501 + :rtype: int + """ + return self._terminal_link_throughput + + @terminal_link_throughput.setter + def terminal_link_throughput(self, terminal_link_throughput): + """Sets the terminal_link_throughput of this NetworkLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :param terminal_link_throughput: The terminal_link_throughput of this NetworkLocation. # noqa: E501 + :type: int + """ + + self._terminal_link_throughput = terminal_link_throughput + + @property + def terminal_link_packet_loss(self): + """Gets the terminal_link_packet_loss of this NetworkLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :return: The terminal_link_packet_loss of this NetworkLocation. # noqa: E501 + :rtype: float + """ + return self._terminal_link_packet_loss + + @terminal_link_packet_loss.setter + def terminal_link_packet_loss(self, terminal_link_packet_loss): + """Sets the terminal_link_packet_loss of this NetworkLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :param terminal_link_packet_loss: The terminal_link_packet_loss of this NetworkLocation. # noqa: E501 + :type: float + """ + + self._terminal_link_packet_loss = terminal_link_packet_loss + + @property + def meta(self): + """Gets the meta of this NetworkLocation. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The meta of this NetworkLocation. # noqa: E501 + :rtype: dict(str, str) + """ + return self._meta + + @meta.setter + def meta(self, meta): + """Sets the meta of this NetworkLocation. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param meta: The meta of this NetworkLocation. # noqa: E501 + :type: dict(str, str) + """ + + self._meta = meta + + @property + def user_meta(self): + """Gets the user_meta of this NetworkLocation. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The user_meta of this NetworkLocation. # noqa: E501 + :rtype: dict(str, str) + """ + return self._user_meta + + @user_meta.setter + def user_meta(self, user_meta): + """Sets the user_meta of this NetworkLocation. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param user_meta: The user_meta of this NetworkLocation. # noqa: E501 + :type: dict(str, str) + """ + + self._user_meta = user_meta + + @property + def cellular_poa_config(self): + """Gets the cellular_poa_config of this NetworkLocation. # noqa: E501 + + + :return: The cellular_poa_config of this NetworkLocation. # noqa: E501 + :rtype: CellularPoaConfig + """ + return self._cellular_poa_config + + @cellular_poa_config.setter + def cellular_poa_config(self, cellular_poa_config): + """Sets the cellular_poa_config of this NetworkLocation. + + + :param cellular_poa_config: The cellular_poa_config of this NetworkLocation. # noqa: E501 + :type: CellularPoaConfig + """ + + self._cellular_poa_config = cellular_poa_config + + @property + def poa4_g_config(self): + """Gets the poa4_g_config of this NetworkLocation. # noqa: E501 + + + :return: The poa4_g_config of this NetworkLocation. # noqa: E501 + :rtype: Poa4GConfig + """ + return self._poa4_g_config + + @poa4_g_config.setter + def poa4_g_config(self, poa4_g_config): + """Sets the poa4_g_config of this NetworkLocation. + + + :param poa4_g_config: The poa4_g_config of this NetworkLocation. # noqa: E501 + :type: Poa4GConfig + """ + + self._poa4_g_config = poa4_g_config + + @property + def poa5_g_config(self): + """Gets the poa5_g_config of this NetworkLocation. # noqa: E501 + + + :return: The poa5_g_config of this NetworkLocation. # noqa: E501 + :rtype: Poa5GConfig + """ + return self._poa5_g_config + + @poa5_g_config.setter + def poa5_g_config(self, poa5_g_config): + """Sets the poa5_g_config of this NetworkLocation. + + + :param poa5_g_config: The poa5_g_config of this NetworkLocation. # noqa: E501 + :type: Poa5GConfig + """ + + self._poa5_g_config = poa5_g_config + + @property + def poa_wifi_config(self): + """Gets the poa_wifi_config of this NetworkLocation. # noqa: E501 + + + :return: The poa_wifi_config of this NetworkLocation. # noqa: E501 + :rtype: PoaWifiConfig + """ + return self._poa_wifi_config + + @poa_wifi_config.setter + def poa_wifi_config(self, poa_wifi_config): + """Sets the poa_wifi_config of this NetworkLocation. + + + :param poa_wifi_config: The poa_wifi_config of this NetworkLocation. # noqa: E501 + :type: PoaWifiConfig + """ + + self._poa_wifi_config = poa_wifi_config + + @property + def geo_data(self): + """Gets the geo_data of this NetworkLocation. # noqa: E501 + + + :return: The geo_data of this NetworkLocation. # noqa: E501 + :rtype: GeoData + """ + return self._geo_data + + @geo_data.setter + def geo_data(self, geo_data): + """Sets the geo_data of this NetworkLocation. + + + :param geo_data: The geo_data of this NetworkLocation. # noqa: E501 + :type: GeoData + """ + + self._geo_data = geo_data + + @property + def physical_locations(self): + """Gets the physical_locations of this NetworkLocation. # noqa: E501 + + + :return: The physical_locations of this NetworkLocation. # noqa: E501 + :rtype: list[PhysicalLocation] + """ + return self._physical_locations + + @physical_locations.setter + def physical_locations(self, physical_locations): + """Sets the physical_locations of this NetworkLocation. + + + :param physical_locations: The physical_locations of this NetworkLocation. # noqa: E501 + :type: list[PhysicalLocation] + """ + + self._physical_locations = physical_locations + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(NetworkLocation, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, NetworkLocation): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/oauth.py b/examples/demo9/python/mecapp/swagger_client/models/oauth.py new file mode 100644 index 0000000000000000000000000000000000000000..158067aaed700cee9d032c534fe548a30cd98410 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/oauth.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Oauth(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'user_code': 'str', + 'verification_uri': 'str' + } + + attribute_map = { + 'user_code': 'user_code', + 'verification_uri': 'verification_uri' + } + + def __init__(self, user_code=None, verification_uri=None): # noqa: E501 + """Oauth - a model defined in Swagger""" # noqa: E501 + self._user_code = None + self._verification_uri = None + self.discriminator = None + if user_code is not None: + self.user_code = user_code + if verification_uri is not None: + self.verification_uri = verification_uri + + @property + def user_code(self): + """Gets the user_code of this Oauth. # noqa: E501 + + User code from the authentication provider # noqa: E501 + + :return: The user_code of this Oauth. # noqa: E501 + :rtype: str + """ + return self._user_code + + @user_code.setter + def user_code(self, user_code): + """Sets the user_code of this Oauth. + + User code from the authentication provider # noqa: E501 + + :param user_code: The user_code of this Oauth. # noqa: E501 + :type: str + """ + + self._user_code = user_code + + @property + def verification_uri(self): + """Gets the verification_uri of this Oauth. # noqa: E501 + + Verification URI to go to and enter the user code in order to authenticate # noqa: E501 + + :return: The verification_uri of this Oauth. # noqa: E501 + :rtype: str + """ + return self._verification_uri + + @verification_uri.setter + def verification_uri(self, verification_uri): + """Sets the verification_uri of this Oauth. + + Verification URI to go to and enter the user code in order to authenticate # noqa: E501 + + :param verification_uri: The verification_uri of this Oauth. # noqa: E501 + :type: str + """ + + self._verification_uri = verification_uri + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Oauth, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Oauth): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/physical_location.py b/examples/demo9/python/mecapp/swagger_client/models/physical_location.py new file mode 100644 index 0000000000000000000000000000000000000000..586d0500515ea5cb7bd57ea84a5c7dc39c51498c --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/physical_location.py @@ -0,0 +1,612 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class PhysicalLocation(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'name': 'str', + 'type': 'str', + 'is_external': 'bool', + 'geo_data': 'GeoData', + 'network_locations_in_range': 'list[str]', + 'connected': 'bool', + 'wireless': 'bool', + 'wireless_type': 'str', + 'data_network': 'DNConfig', + 'meta': 'dict(str, str)', + 'user_meta': 'dict(str, str)', + 'processes': 'list[Process]', + 'net_char': 'NetworkCharacteristics', + 'link_latency': 'int', + 'link_latency_variation': 'int', + 'link_throughput': 'int', + 'link_packet_loss': 'float', + 'mac_id': 'str' + } + + attribute_map = { + 'id': 'id', + 'name': 'name', + 'type': 'type', + 'is_external': 'isExternal', + 'geo_data': 'geoData', + 'network_locations_in_range': 'networkLocationsInRange', + 'connected': 'connected', + 'wireless': 'wireless', + 'wireless_type': 'wirelessType', + 'data_network': 'dataNetwork', + 'meta': 'meta', + 'user_meta': 'userMeta', + 'processes': 'processes', + 'net_char': 'netChar', + 'link_latency': 'linkLatency', + 'link_latency_variation': 'linkLatencyVariation', + 'link_throughput': 'linkThroughput', + 'link_packet_loss': 'linkPacketLoss', + 'mac_id': 'macId' + } + + def __init__(self, id=None, name=None, type=None, is_external=None, geo_data=None, network_locations_in_range=None, connected=None, wireless=None, wireless_type=None, data_network=None, meta=None, user_meta=None, processes=None, net_char=None, link_latency=None, link_latency_variation=None, link_throughput=None, link_packet_loss=None, mac_id=None): # noqa: E501 + """PhysicalLocation - a model defined in Swagger""" # noqa: E501 + self._id = None + self._name = None + self._type = None + self._is_external = None + self._geo_data = None + self._network_locations_in_range = None + self._connected = None + self._wireless = None + self._wireless_type = None + self._data_network = None + self._meta = None + self._user_meta = None + self._processes = None + self._net_char = None + self._link_latency = None + self._link_latency_variation = None + self._link_throughput = None + self._link_packet_loss = None + self._mac_id = None + self.discriminator = None + if id is not None: + self.id = id + if name is not None: + self.name = name + if type is not None: + self.type = type + if is_external is not None: + self.is_external = is_external + if geo_data is not None: + self.geo_data = geo_data + if network_locations_in_range is not None: + self.network_locations_in_range = network_locations_in_range + if connected is not None: + self.connected = connected + if wireless is not None: + self.wireless = wireless + if wireless_type is not None: + self.wireless_type = wireless_type + if data_network is not None: + self.data_network = data_network + if meta is not None: + self.meta = meta + if user_meta is not None: + self.user_meta = user_meta + if processes is not None: + self.processes = processes + if net_char is not None: + self.net_char = net_char + if link_latency is not None: + self.link_latency = link_latency + if link_latency_variation is not None: + self.link_latency_variation = link_latency_variation + if link_throughput is not None: + self.link_throughput = link_throughput + if link_packet_loss is not None: + self.link_packet_loss = link_packet_loss + if mac_id is not None: + self.mac_id = mac_id + + @property + def id(self): + """Gets the id of this PhysicalLocation. # noqa: E501 + + Unique physical location ID # noqa: E501 + + :return: The id of this PhysicalLocation. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this PhysicalLocation. + + Unique physical location ID # noqa: E501 + + :param id: The id of this PhysicalLocation. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this PhysicalLocation. # noqa: E501 + + Physical location name # noqa: E501 + + :return: The name of this PhysicalLocation. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this PhysicalLocation. + + Physical location name # noqa: E501 + + :param name: The name of this PhysicalLocation. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def type(self): + """Gets the type of this PhysicalLocation. # noqa: E501 + + Physical location type # noqa: E501 + + :return: The type of this PhysicalLocation. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this PhysicalLocation. + + Physical location type # noqa: E501 + + :param type: The type of this PhysicalLocation. # noqa: E501 + :type: str + """ + allowed_values = ["UE", "FOG", "EDGE", "CN", "DC"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def is_external(self): + """Gets the is_external of this PhysicalLocation. # noqa: E501 + + true: Physical location is external to MEEP false: Physical location is internal to MEEP # noqa: E501 + + :return: The is_external of this PhysicalLocation. # noqa: E501 + :rtype: bool + """ + return self._is_external + + @is_external.setter + def is_external(self, is_external): + """Sets the is_external of this PhysicalLocation. + + true: Physical location is external to MEEP false: Physical location is internal to MEEP # noqa: E501 + + :param is_external: The is_external of this PhysicalLocation. # noqa: E501 + :type: bool + """ + + self._is_external = is_external + + @property + def geo_data(self): + """Gets the geo_data of this PhysicalLocation. # noqa: E501 + + + :return: The geo_data of this PhysicalLocation. # noqa: E501 + :rtype: GeoData + """ + return self._geo_data + + @geo_data.setter + def geo_data(self, geo_data): + """Sets the geo_data of this PhysicalLocation. + + + :param geo_data: The geo_data of this PhysicalLocation. # noqa: E501 + :type: GeoData + """ + + self._geo_data = geo_data + + @property + def network_locations_in_range(self): + """Gets the network_locations_in_range of this PhysicalLocation. # noqa: E501 + + + :return: The network_locations_in_range of this PhysicalLocation. # noqa: E501 + :rtype: list[str] + """ + return self._network_locations_in_range + + @network_locations_in_range.setter + def network_locations_in_range(self, network_locations_in_range): + """Sets the network_locations_in_range of this PhysicalLocation. + + + :param network_locations_in_range: The network_locations_in_range of this PhysicalLocation. # noqa: E501 + :type: list[str] + """ + + self._network_locations_in_range = network_locations_in_range + + @property + def connected(self): + """Gets the connected of this PhysicalLocation. # noqa: E501 + + true: Physical location has network connectivity false: Physical location has no network connectivity # noqa: E501 + + :return: The connected of this PhysicalLocation. # noqa: E501 + :rtype: bool + """ + return self._connected + + @connected.setter + def connected(self, connected): + """Sets the connected of this PhysicalLocation. + + true: Physical location has network connectivity false: Physical location has no network connectivity # noqa: E501 + + :param connected: The connected of this PhysicalLocation. # noqa: E501 + :type: bool + """ + + self._connected = connected + + @property + def wireless(self): + """Gets the wireless of this PhysicalLocation. # noqa: E501 + + true: Physical location uses a wireless connection false: Physical location uses a wired connection # noqa: E501 + + :return: The wireless of this PhysicalLocation. # noqa: E501 + :rtype: bool + """ + return self._wireless + + @wireless.setter + def wireless(self, wireless): + """Sets the wireless of this PhysicalLocation. + + true: Physical location uses a wireless connection false: Physical location uses a wired connection # noqa: E501 + + :param wireless: The wireless of this PhysicalLocation. # noqa: E501 + :type: bool + """ + + self._wireless = wireless + + @property + def wireless_type(self): + """Gets the wireless_type of this PhysicalLocation. # noqa: E501 + + Prioritized, comma-separated list of supported wireless connection types. Default priority if not specififed is 'wifi,5g,4g,other'. Wireless connection types: - 4g - 5g - wifi - other # noqa: E501 + + :return: The wireless_type of this PhysicalLocation. # noqa: E501 + :rtype: str + """ + return self._wireless_type + + @wireless_type.setter + def wireless_type(self, wireless_type): + """Sets the wireless_type of this PhysicalLocation. + + Prioritized, comma-separated list of supported wireless connection types. Default priority if not specififed is 'wifi,5g,4g,other'. Wireless connection types: - 4g - 5g - wifi - other # noqa: E501 + + :param wireless_type: The wireless_type of this PhysicalLocation. # noqa: E501 + :type: str + """ + + self._wireless_type = wireless_type + + @property + def data_network(self): + """Gets the data_network of this PhysicalLocation. # noqa: E501 + + + :return: The data_network of this PhysicalLocation. # noqa: E501 + :rtype: DNConfig + """ + return self._data_network + + @data_network.setter + def data_network(self, data_network): + """Sets the data_network of this PhysicalLocation. + + + :param data_network: The data_network of this PhysicalLocation. # noqa: E501 + :type: DNConfig + """ + + self._data_network = data_network + + @property + def meta(self): + """Gets the meta of this PhysicalLocation. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The meta of this PhysicalLocation. # noqa: E501 + :rtype: dict(str, str) + """ + return self._meta + + @meta.setter + def meta(self, meta): + """Sets the meta of this PhysicalLocation. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param meta: The meta of this PhysicalLocation. # noqa: E501 + :type: dict(str, str) + """ + + self._meta = meta + + @property + def user_meta(self): + """Gets the user_meta of this PhysicalLocation. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The user_meta of this PhysicalLocation. # noqa: E501 + :rtype: dict(str, str) + """ + return self._user_meta + + @user_meta.setter + def user_meta(self, user_meta): + """Sets the user_meta of this PhysicalLocation. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param user_meta: The user_meta of this PhysicalLocation. # noqa: E501 + :type: dict(str, str) + """ + + self._user_meta = user_meta + + @property + def processes(self): + """Gets the processes of this PhysicalLocation. # noqa: E501 + + + :return: The processes of this PhysicalLocation. # noqa: E501 + :rtype: list[Process] + """ + return self._processes + + @processes.setter + def processes(self, processes): + """Sets the processes of this PhysicalLocation. + + + :param processes: The processes of this PhysicalLocation. # noqa: E501 + :type: list[Process] + """ + + self._processes = processes + + @property + def net_char(self): + """Gets the net_char of this PhysicalLocation. # noqa: E501 + + + :return: The net_char of this PhysicalLocation. # noqa: E501 + :rtype: NetworkCharacteristics + """ + return self._net_char + + @net_char.setter + def net_char(self, net_char): + """Sets the net_char of this PhysicalLocation. + + + :param net_char: The net_char of this PhysicalLocation. # noqa: E501 + :type: NetworkCharacteristics + """ + + self._net_char = net_char + + @property + def link_latency(self): + """Gets the link_latency of this PhysicalLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :return: The link_latency of this PhysicalLocation. # noqa: E501 + :rtype: int + """ + return self._link_latency + + @link_latency.setter + def link_latency(self, link_latency): + """Sets the link_latency of this PhysicalLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :param link_latency: The link_latency of this PhysicalLocation. # noqa: E501 + :type: int + """ + + self._link_latency = link_latency + + @property + def link_latency_variation(self): + """Gets the link_latency_variation of this PhysicalLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :return: The link_latency_variation of this PhysicalLocation. # noqa: E501 + :rtype: int + """ + return self._link_latency_variation + + @link_latency_variation.setter + def link_latency_variation(self, link_latency_variation): + """Sets the link_latency_variation of this PhysicalLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :param link_latency_variation: The link_latency_variation of this PhysicalLocation. # noqa: E501 + :type: int + """ + + self._link_latency_variation = link_latency_variation + + @property + def link_throughput(self): + """Gets the link_throughput of this PhysicalLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :return: The link_throughput of this PhysicalLocation. # noqa: E501 + :rtype: int + """ + return self._link_throughput + + @link_throughput.setter + def link_throughput(self, link_throughput): + """Sets the link_throughput of this PhysicalLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :param link_throughput: The link_throughput of this PhysicalLocation. # noqa: E501 + :type: int + """ + + self._link_throughput = link_throughput + + @property + def link_packet_loss(self): + """Gets the link_packet_loss of this PhysicalLocation. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :return: The link_packet_loss of this PhysicalLocation. # noqa: E501 + :rtype: float + """ + return self._link_packet_loss + + @link_packet_loss.setter + def link_packet_loss(self, link_packet_loss): + """Sets the link_packet_loss of this PhysicalLocation. + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :param link_packet_loss: The link_packet_loss of this PhysicalLocation. # noqa: E501 + :type: float + """ + + self._link_packet_loss = link_packet_loss + + @property + def mac_id(self): + """Gets the mac_id of this PhysicalLocation. # noqa: E501 + + Physical location MAC Address # noqa: E501 + + :return: The mac_id of this PhysicalLocation. # noqa: E501 + :rtype: str + """ + return self._mac_id + + @mac_id.setter + def mac_id(self, mac_id): + """Sets the mac_id of this PhysicalLocation. + + Physical location MAC Address # noqa: E501 + + :param mac_id: The mac_id of this PhysicalLocation. # noqa: E501 + :type: str + """ + + self._mac_id = mac_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(PhysicalLocation, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, PhysicalLocation): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/poa4_g_config.py b/examples/demo9/python/mecapp/swagger_client/models/poa4_g_config.py new file mode 100644 index 0000000000000000000000000000000000000000..ffacd20a2b2333a67ae501f0b75d21167b9e4146 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/poa4_g_config.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Poa4GConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'cell_id': 'str' + } + + attribute_map = { + 'cell_id': 'cellId' + } + + def __init__(self, cell_id=None): # noqa: E501 + """Poa4GConfig - a model defined in Swagger""" # noqa: E501 + self._cell_id = None + self.discriminator = None + if cell_id is not None: + self.cell_id = cell_id + + @property + def cell_id(self): + """Gets the cell_id of this Poa4GConfig. # noqa: E501 + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell # noqa: E501 + + :return: The cell_id of this Poa4GConfig. # noqa: E501 + :rtype: str + """ + return self._cell_id + + @cell_id.setter + def cell_id(self, cell_id): + """Sets the cell_id of this Poa4GConfig. + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the eNB serving the cell # noqa: E501 + + :param cell_id: The cell_id of this Poa4GConfig. # noqa: E501 + :type: str + """ + + self._cell_id = cell_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Poa4GConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Poa4GConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/poa5_g_config.py b/examples/demo9/python/mecapp/swagger_client/models/poa5_g_config.py new file mode 100644 index 0000000000000000000000000000000000000000..441d0d7778d961f524b4115425fcb5613f0810f6 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/poa5_g_config.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Poa5GConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'cell_id': 'str' + } + + attribute_map = { + 'cell_id': 'cellId' + } + + def __init__(self, cell_id=None): # noqa: E501 + """Poa5GConfig - a model defined in Swagger""" # noqa: E501 + self._cell_id = None + self.discriminator = None + if cell_id is not None: + self.cell_id = cell_id + + @property + def cell_id(self): + """Gets the cell_id of this Poa5GConfig. # noqa: E501 + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the NR serving the cell # noqa: E501 + + :return: The cell_id of this Poa5GConfig. # noqa: E501 + :rtype: str + """ + return self._cell_id + + @cell_id.setter + def cell_id(self, cell_id): + """Sets the cell_id of this Poa5GConfig. + + The E-UTRAN Cell Identity as defined in ETSI TS 136 413 including the ID of the NR serving the cell # noqa: E501 + + :param cell_id: The cell_id of this Poa5GConfig. # noqa: E501 + :type: str + """ + + self._cell_id = cell_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Poa5GConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Poa5GConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/poa_wifi_config.py b/examples/demo9/python/mecapp/swagger_client/models/poa_wifi_config.py new file mode 100644 index 0000000000000000000000000000000000000000..a7666258809001b1679abe755ef5aebd2f477aff --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/poa_wifi_config.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class PoaWifiConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'mac_id': 'str' + } + + attribute_map = { + 'mac_id': 'macId' + } + + def __init__(self, mac_id=None): # noqa: E501 + """PoaWifiConfig - a model defined in Swagger""" # noqa: E501 + self._mac_id = None + self.discriminator = None + if mac_id is not None: + self.mac_id = mac_id + + @property + def mac_id(self): + """Gets the mac_id of this PoaWifiConfig. # noqa: E501 + + WIFI POA MAC Address # noqa: E501 + + :return: The mac_id of this PoaWifiConfig. # noqa: E501 + :rtype: str + """ + return self._mac_id + + @mac_id.setter + def mac_id(self, mac_id): + """Sets the mac_id of this PoaWifiConfig. + + WIFI POA MAC Address # noqa: E501 + + :param mac_id: The mac_id of this PoaWifiConfig. # noqa: E501 + :type: str + """ + + self._mac_id = mac_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(PoaWifiConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, PoaWifiConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/point.py b/examples/demo9/python/mecapp/swagger_client/models/point.py new file mode 100644 index 0000000000000000000000000000000000000000..c5598afd135a9ee2d56dd2fa221af7f5721ba40a --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/point.py @@ -0,0 +1,147 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Point(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'type': 'str', + 'coordinates': 'list[float]' + } + + attribute_map = { + 'type': 'type', + 'coordinates': 'coordinates' + } + + def __init__(self, type=None, coordinates=None): # noqa: E501 + """Point - a model defined in Swagger""" # noqa: E501 + self._type = None + self._coordinates = None + self.discriminator = None + self.type = type + if coordinates is not None: + self.coordinates = coordinates + + @property + def type(self): + """Gets the type of this Point. # noqa: E501 + + Must be Point # noqa: E501 + + :return: The type of this Point. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this Point. + + Must be Point # noqa: E501 + + :param type: The type of this Point. # noqa: E501 + :type: str + """ + if type is None: + raise ValueError("Invalid value for `type`, must not be `None`") # noqa: E501 + allowed_values = ["Point"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def coordinates(self): + """Gets the coordinates of this Point. # noqa: E501 + + For a Point, coordinates MUST be an array of two decimal numbers; longitude and latitude precisely in that order # noqa: E501 + + :return: The coordinates of this Point. # noqa: E501 + :rtype: list[float] + """ + return self._coordinates + + @coordinates.setter + def coordinates(self, coordinates): + """Sets the coordinates of this Point. + + For a Point, coordinates MUST be an array of two decimal numbers; longitude and latitude precisely in that order # noqa: E501 + + :param coordinates: The coordinates of this Point. # noqa: E501 + :type: list[float] + """ + + self._coordinates = coordinates + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Point, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Point): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/problem_details.py b/examples/demo9/python/mecapp/swagger_client/models/problem_details.py new file mode 100644 index 0000000000000000000000000000000000000000..01f1e7387129ad6314eab8adaefbfa3f898987cb --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/problem_details.py @@ -0,0 +1,226 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ProblemDetails(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'type': 'str', + 'title': 'str', + 'status': 'int', + 'detail': 'str', + 'instance': 'str' + } + + attribute_map = { + 'type': 'type', + 'title': 'title', + 'status': 'status', + 'detail': 'detail', + 'instance': 'instance' + } + + def __init__(self, type=None, title=None, status=None, detail=None, instance=None): # noqa: E501 + """ProblemDetails - a model defined in Swagger""" # noqa: E501 + self._type = None + self._title = None + self._status = None + self._detail = None + self._instance = None + self.discriminator = None + if type is not None: + self.type = type + if title is not None: + self.title = title + self.status = status + self.detail = detail + if instance is not None: + self.instance = instance + + @property + def type(self): + """Gets the type of this ProblemDetails. # noqa: E501 + + A URI reference according to IETF RFC 3986 that identifies the problem type. It is encouraged that the URI provides human-readable documentation for the problem (e.g. using HTML) when dereferenced. When this member is not present, its value is assumed to be \"about:blank\". # noqa: E501 + + :return: The type of this ProblemDetails. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this ProblemDetails. + + A URI reference according to IETF RFC 3986 that identifies the problem type. It is encouraged that the URI provides human-readable documentation for the problem (e.g. using HTML) when dereferenced. When this member is not present, its value is assumed to be \"about:blank\". # noqa: E501 + + :param type: The type of this ProblemDetails. # noqa: E501 + :type: str + """ + + self._type = type + + @property + def title(self): + """Gets the title of this ProblemDetails. # noqa: E501 + + A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization. If type is given and other than \"about:blank\", this attribute shall also be provided. A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4). # noqa: E501 + + :return: The title of this ProblemDetails. # noqa: E501 + :rtype: str + """ + return self._title + + @title.setter + def title(self, title): + """Sets the title of this ProblemDetails. + + A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization. If type is given and other than \"about:blank\", this attribute shall also be provided. A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4). # noqa: E501 + + :param title: The title of this ProblemDetails. # noqa: E501 + :type: str + """ + + self._title = title + + @property + def status(self): + """Gets the status of this ProblemDetails. # noqa: E501 + + The HTTP status code for this occurrence of the problem. The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. # noqa: E501 + + :return: The status of this ProblemDetails. # noqa: E501 + :rtype: int + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this ProblemDetails. + + The HTTP status code for this occurrence of the problem. The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. # noqa: E501 + + :param status: The status of this ProblemDetails. # noqa: E501 + :type: int + """ + if status is None: + raise ValueError("Invalid value for `status`, must not be `None`") # noqa: E501 + + self._status = status + + @property + def detail(self): + """Gets the detail of this ProblemDetails. # noqa: E501 + + A human-readable explanation specific to this occurrence of the problem. # noqa: E501 + + :return: The detail of this ProblemDetails. # noqa: E501 + :rtype: str + """ + return self._detail + + @detail.setter + def detail(self, detail): + """Sets the detail of this ProblemDetails. + + A human-readable explanation specific to this occurrence of the problem. # noqa: E501 + + :param detail: The detail of this ProblemDetails. # noqa: E501 + :type: str + """ + if detail is None: + raise ValueError("Invalid value for `detail`, must not be `None`") # noqa: E501 + + self._detail = detail + + @property + def instance(self): + """Gets the instance of this ProblemDetails. # noqa: E501 + + A URI reference that identifies the specific occurrence of the problem. It may yield further information if dereferenced. # noqa: E501 + + :return: The instance of this ProblemDetails. # noqa: E501 + :rtype: str + """ + return self._instance + + @instance.setter + def instance(self, instance): + """Sets the instance of this ProblemDetails. + + A URI reference that identifies the specific occurrence of the problem. It may yield further information if dereferenced. # noqa: E501 + + :param instance: The instance of this ProblemDetails. # noqa: E501 + :type: str + """ + + self._instance = instance + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ProblemDetails, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ProblemDetails): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/process.py b/examples/demo9/python/mecapp/swagger_client/models/process.py new file mode 100644 index 0000000000000000000000000000000000000000..1015cd633cb19ca75539d999b045b6046a63bf93 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/process.py @@ -0,0 +1,778 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Process(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'name': 'str', + 'type': 'str', + 'is_external': 'bool', + 'image': 'str', + 'environment': 'str', + 'command_arguments': 'str', + 'command_exe': 'str', + 'service_config': 'ServiceConfig', + 'gpu_config': 'GpuConfig', + 'memory_config': 'MemoryConfig', + 'cpu_config': 'CpuConfig', + 'external_config': 'ExternalConfig', + 'status': 'str', + 'user_chart_location': 'str', + 'user_chart_alternate_values': 'str', + 'user_chart_group': 'str', + 'meta': 'dict(str, str)', + 'user_meta': 'dict(str, str)', + 'net_char': 'NetworkCharacteristics', + 'app_latency': 'int', + 'app_latency_variation': 'int', + 'app_throughput': 'int', + 'app_packet_loss': 'float', + 'placement_id': 'str' + } + + attribute_map = { + 'id': 'id', + 'name': 'name', + 'type': 'type', + 'is_external': 'isExternal', + 'image': 'image', + 'environment': 'environment', + 'command_arguments': 'commandArguments', + 'command_exe': 'commandExe', + 'service_config': 'serviceConfig', + 'gpu_config': 'gpuConfig', + 'memory_config': 'memoryConfig', + 'cpu_config': 'cpuConfig', + 'external_config': 'externalConfig', + 'status': 'status', + 'user_chart_location': 'userChartLocation', + 'user_chart_alternate_values': 'userChartAlternateValues', + 'user_chart_group': 'userChartGroup', + 'meta': 'meta', + 'user_meta': 'userMeta', + 'net_char': 'netChar', + 'app_latency': 'appLatency', + 'app_latency_variation': 'appLatencyVariation', + 'app_throughput': 'appThroughput', + 'app_packet_loss': 'appPacketLoss', + 'placement_id': 'placementId' + } + + def __init__(self, id=None, name=None, type=None, is_external=None, image=None, environment=None, command_arguments=None, command_exe=None, service_config=None, gpu_config=None, memory_config=None, cpu_config=None, external_config=None, status=None, user_chart_location=None, user_chart_alternate_values=None, user_chart_group=None, meta=None, user_meta=None, net_char=None, app_latency=None, app_latency_variation=None, app_throughput=None, app_packet_loss=None, placement_id=None): # noqa: E501 + """Process - a model defined in Swagger""" # noqa: E501 + self._id = None + self._name = None + self._type = None + self._is_external = None + self._image = None + self._environment = None + self._command_arguments = None + self._command_exe = None + self._service_config = None + self._gpu_config = None + self._memory_config = None + self._cpu_config = None + self._external_config = None + self._status = None + self._user_chart_location = None + self._user_chart_alternate_values = None + self._user_chart_group = None + self._meta = None + self._user_meta = None + self._net_char = None + self._app_latency = None + self._app_latency_variation = None + self._app_throughput = None + self._app_packet_loss = None + self._placement_id = None + self.discriminator = None + if id is not None: + self.id = id + if name is not None: + self.name = name + if type is not None: + self.type = type + if is_external is not None: + self.is_external = is_external + if image is not None: + self.image = image + if environment is not None: + self.environment = environment + if command_arguments is not None: + self.command_arguments = command_arguments + if command_exe is not None: + self.command_exe = command_exe + if service_config is not None: + self.service_config = service_config + if gpu_config is not None: + self.gpu_config = gpu_config + if memory_config is not None: + self.memory_config = memory_config + if cpu_config is not None: + self.cpu_config = cpu_config + if external_config is not None: + self.external_config = external_config + if status is not None: + self.status = status + if user_chart_location is not None: + self.user_chart_location = user_chart_location + if user_chart_alternate_values is not None: + self.user_chart_alternate_values = user_chart_alternate_values + if user_chart_group is not None: + self.user_chart_group = user_chart_group + if meta is not None: + self.meta = meta + if user_meta is not None: + self.user_meta = user_meta + if net_char is not None: + self.net_char = net_char + if app_latency is not None: + self.app_latency = app_latency + if app_latency_variation is not None: + self.app_latency_variation = app_latency_variation + if app_throughput is not None: + self.app_throughput = app_throughput + if app_packet_loss is not None: + self.app_packet_loss = app_packet_loss + if placement_id is not None: + self.placement_id = placement_id + + @property + def id(self): + """Gets the id of this Process. # noqa: E501 + + Unique process ID # noqa: E501 + + :return: The id of this Process. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Process. + + Unique process ID # noqa: E501 + + :param id: The id of this Process. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this Process. # noqa: E501 + + Process name # noqa: E501 + + :return: The name of this Process. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Process. + + Process name # noqa: E501 + + :param name: The name of this Process. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def type(self): + """Gets the type of this Process. # noqa: E501 + + Process type # noqa: E501 + + :return: The type of this Process. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this Process. + + Process type # noqa: E501 + + :param type: The type of this Process. # noqa: E501 + :type: str + """ + allowed_values = ["UE-APP", "EDGE-APP", "MEC-SVC", "CLOUD-APP"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def is_external(self): + """Gets the is_external of this Process. # noqa: E501 + + true: process is external to MEEP false: process is internal to MEEP # noqa: E501 + + :return: The is_external of this Process. # noqa: E501 + :rtype: bool + """ + return self._is_external + + @is_external.setter + def is_external(self, is_external): + """Sets the is_external of this Process. + + true: process is external to MEEP false: process is internal to MEEP # noqa: E501 + + :param is_external: The is_external of this Process. # noqa: E501 + :type: bool + """ + + self._is_external = is_external + + @property + def image(self): + """Gets the image of this Process. # noqa: E501 + + Docker image to deploy inside MEEP # noqa: E501 + + :return: The image of this Process. # noqa: E501 + :rtype: str + """ + return self._image + + @image.setter + def image(self, image): + """Sets the image of this Process. + + Docker image to deploy inside MEEP # noqa: E501 + + :param image: The image of this Process. # noqa: E501 + :type: str + """ + + self._image = image + + @property + def environment(self): + """Gets the environment of this Process. # noqa: E501 + + Environment variables using the format NAME=\"value\",NAME=\"value\",NAME=\"value\" # noqa: E501 + + :return: The environment of this Process. # noqa: E501 + :rtype: str + """ + return self._environment + + @environment.setter + def environment(self, environment): + """Sets the environment of this Process. + + Environment variables using the format NAME=\"value\",NAME=\"value\",NAME=\"value\" # noqa: E501 + + :param environment: The environment of this Process. # noqa: E501 + :type: str + """ + + self._environment = environment + + @property + def command_arguments(self): + """Gets the command_arguments of this Process. # noqa: E501 + + Arguments to command executable # noqa: E501 + + :return: The command_arguments of this Process. # noqa: E501 + :rtype: str + """ + return self._command_arguments + + @command_arguments.setter + def command_arguments(self, command_arguments): + """Sets the command_arguments of this Process. + + Arguments to command executable # noqa: E501 + + :param command_arguments: The command_arguments of this Process. # noqa: E501 + :type: str + """ + + self._command_arguments = command_arguments + + @property + def command_exe(self): + """Gets the command_exe of this Process. # noqa: E501 + + Executable to invoke at container start up # noqa: E501 + + :return: The command_exe of this Process. # noqa: E501 + :rtype: str + """ + return self._command_exe + + @command_exe.setter + def command_exe(self, command_exe): + """Sets the command_exe of this Process. + + Executable to invoke at container start up # noqa: E501 + + :param command_exe: The command_exe of this Process. # noqa: E501 + :type: str + """ + + self._command_exe = command_exe + + @property + def service_config(self): + """Gets the service_config of this Process. # noqa: E501 + + + :return: The service_config of this Process. # noqa: E501 + :rtype: ServiceConfig + """ + return self._service_config + + @service_config.setter + def service_config(self, service_config): + """Sets the service_config of this Process. + + + :param service_config: The service_config of this Process. # noqa: E501 + :type: ServiceConfig + """ + + self._service_config = service_config + + @property + def gpu_config(self): + """Gets the gpu_config of this Process. # noqa: E501 + + + :return: The gpu_config of this Process. # noqa: E501 + :rtype: GpuConfig + """ + return self._gpu_config + + @gpu_config.setter + def gpu_config(self, gpu_config): + """Sets the gpu_config of this Process. + + + :param gpu_config: The gpu_config of this Process. # noqa: E501 + :type: GpuConfig + """ + + self._gpu_config = gpu_config + + @property + def memory_config(self): + """Gets the memory_config of this Process. # noqa: E501 + + + :return: The memory_config of this Process. # noqa: E501 + :rtype: MemoryConfig + """ + return self._memory_config + + @memory_config.setter + def memory_config(self, memory_config): + """Sets the memory_config of this Process. + + + :param memory_config: The memory_config of this Process. # noqa: E501 + :type: MemoryConfig + """ + + self._memory_config = memory_config + + @property + def cpu_config(self): + """Gets the cpu_config of this Process. # noqa: E501 + + + :return: The cpu_config of this Process. # noqa: E501 + :rtype: CpuConfig + """ + return self._cpu_config + + @cpu_config.setter + def cpu_config(self, cpu_config): + """Sets the cpu_config of this Process. + + + :param cpu_config: The cpu_config of this Process. # noqa: E501 + :type: CpuConfig + """ + + self._cpu_config = cpu_config + + @property + def external_config(self): + """Gets the external_config of this Process. # noqa: E501 + + + :return: The external_config of this Process. # noqa: E501 + :rtype: ExternalConfig + """ + return self._external_config + + @external_config.setter + def external_config(self, external_config): + """Sets the external_config of this Process. + + + :param external_config: The external_config of this Process. # noqa: E501 + :type: ExternalConfig + """ + + self._external_config = external_config + + @property + def status(self): + """Gets the status of this Process. # noqa: E501 + + Process status # noqa: E501 + + :return: The status of this Process. # noqa: E501 + :rtype: str + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this Process. + + Process status # noqa: E501 + + :param status: The status of this Process. # noqa: E501 + :type: str + """ + + self._status = status + + @property + def user_chart_location(self): + """Gets the user_chart_location of this Process. # noqa: E501 + + Chart location for the deployment of the chart provided by the user # noqa: E501 + + :return: The user_chart_location of this Process. # noqa: E501 + :rtype: str + """ + return self._user_chart_location + + @user_chart_location.setter + def user_chart_location(self, user_chart_location): + """Sets the user_chart_location of this Process. + + Chart location for the deployment of the chart provided by the user # noqa: E501 + + :param user_chart_location: The user_chart_location of this Process. # noqa: E501 + :type: str + """ + + self._user_chart_location = user_chart_location + + @property + def user_chart_alternate_values(self): + """Gets the user_chart_alternate_values of this Process. # noqa: E501 + + Chart values.yaml file location for the deployment of the chart provided by the user # noqa: E501 + + :return: The user_chart_alternate_values of this Process. # noqa: E501 + :rtype: str + """ + return self._user_chart_alternate_values + + @user_chart_alternate_values.setter + def user_chart_alternate_values(self, user_chart_alternate_values): + """Sets the user_chart_alternate_values of this Process. + + Chart values.yaml file location for the deployment of the chart provided by the user # noqa: E501 + + :param user_chart_alternate_values: The user_chart_alternate_values of this Process. # noqa: E501 + :type: str + """ + + self._user_chart_alternate_values = user_chart_alternate_values + + @property + def user_chart_group(self): + """Gets the user_chart_group of this Process. # noqa: E501 + + Chart supplemental information related to the group (service) # noqa: E501 + + :return: The user_chart_group of this Process. # noqa: E501 + :rtype: str + """ + return self._user_chart_group + + @user_chart_group.setter + def user_chart_group(self, user_chart_group): + """Sets the user_chart_group of this Process. + + Chart supplemental information related to the group (service) # noqa: E501 + + :param user_chart_group: The user_chart_group of this Process. # noqa: E501 + :type: str + """ + + self._user_chart_group = user_chart_group + + @property + def meta(self): + """Gets the meta of this Process. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The meta of this Process. # noqa: E501 + :rtype: dict(str, str) + """ + return self._meta + + @meta.setter + def meta(self, meta): + """Sets the meta of this Process. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param meta: The meta of this Process. # noqa: E501 + :type: dict(str, str) + """ + + self._meta = meta + + @property + def user_meta(self): + """Gets the user_meta of this Process. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The user_meta of this Process. # noqa: E501 + :rtype: dict(str, str) + """ + return self._user_meta + + @user_meta.setter + def user_meta(self, user_meta): + """Sets the user_meta of this Process. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param user_meta: The user_meta of this Process. # noqa: E501 + :type: dict(str, str) + """ + + self._user_meta = user_meta + + @property + def net_char(self): + """Gets the net_char of this Process. # noqa: E501 + + + :return: The net_char of this Process. # noqa: E501 + :rtype: NetworkCharacteristics + """ + return self._net_char + + @net_char.setter + def net_char(self, net_char): + """Sets the net_char of this Process. + + + :param net_char: The net_char of this Process. # noqa: E501 + :type: NetworkCharacteristics + """ + + self._net_char = net_char + + @property + def app_latency(self): + """Gets the app_latency of this Process. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :return: The app_latency of this Process. # noqa: E501 + :rtype: int + """ + return self._app_latency + + @app_latency.setter + def app_latency(self, app_latency): + """Sets the app_latency of this Process. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latency # noqa: E501 + + :param app_latency: The app_latency of this Process. # noqa: E501 + :type: int + """ + + self._app_latency = app_latency + + @property + def app_latency_variation(self): + """Gets the app_latency_variation of this Process. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :return: The app_latency_variation of this Process. # noqa: E501 + :rtype: int + """ + return self._app_latency_variation + + @app_latency_variation.setter + def app_latency_variation(self, app_latency_variation): + """Sets the app_latency_variation of this Process. + + **DEPRECATED** As of release 1.5.0, replaced by netChar latencyVariation # noqa: E501 + + :param app_latency_variation: The app_latency_variation of this Process. # noqa: E501 + :type: int + """ + + self._app_latency_variation = app_latency_variation + + @property + def app_throughput(self): + """Gets the app_throughput of this Process. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :return: The app_throughput of this Process. # noqa: E501 + :rtype: int + """ + return self._app_throughput + + @app_throughput.setter + def app_throughput(self, app_throughput): + """Sets the app_throughput of this Process. + + **DEPRECATED** As of release 1.5.0, replaced by netChar throughputUl and throughputDl # noqa: E501 + + :param app_throughput: The app_throughput of this Process. # noqa: E501 + :type: int + """ + + self._app_throughput = app_throughput + + @property + def app_packet_loss(self): + """Gets the app_packet_loss of this Process. # noqa: E501 + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :return: The app_packet_loss of this Process. # noqa: E501 + :rtype: float + """ + return self._app_packet_loss + + @app_packet_loss.setter + def app_packet_loss(self, app_packet_loss): + """Sets the app_packet_loss of this Process. + + **DEPRECATED** As of release 1.5.0, replaced by netChar packetLoss # noqa: E501 + + :param app_packet_loss: The app_packet_loss of this Process. # noqa: E501 + :type: float + """ + + self._app_packet_loss = app_packet_loss + + @property + def placement_id(self): + """Gets the placement_id of this Process. # noqa: E501 + + Identifier used for process placement in AdvantEDGE cluster # noqa: E501 + + :return: The placement_id of this Process. # noqa: E501 + :rtype: str + """ + return self._placement_id + + @placement_id.setter + def placement_id(self, placement_id): + """Sets the placement_id of this Process. + + Identifier used for process placement in AdvantEDGE cluster # noqa: E501 + + :param placement_id: The placement_id of this Process. # noqa: E501 + :type: str + """ + + self._placement_id = placement_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Process, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Process): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/sandbox.py b/examples/demo9/python/mecapp/swagger_client/models/sandbox.py new file mode 100644 index 0000000000000000000000000000000000000000..3630ba159cbdc8f2666b6b4ba5f87238b5c79873 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/sandbox.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Sandbox(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'name': 'str' + } + + attribute_map = { + 'name': 'name' + } + + def __init__(self, name=None): # noqa: E501 + """Sandbox - a model defined in Swagger""" # noqa: E501 + self._name = None + self.discriminator = None + if name is not None: + self.name = name + + @property + def name(self): + """Gets the name of this Sandbox. # noqa: E501 + + Sandbox name # noqa: E501 + + :return: The name of this Sandbox. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Sandbox. + + Sandbox name # noqa: E501 + + :param name: The name of this Sandbox. # noqa: E501 + :type: str + """ + + self._name = name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Sandbox, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Sandbox): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/sandbox_app_instances.py b/examples/demo9/python/mecapp/swagger_client/models/sandbox_app_instances.py new file mode 100644 index 0000000000000000000000000000000000000000..c71fc6fe2cea8a4fa4dffac9c966f41740a44325 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/sandbox_app_instances.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class SandboxAppInstances(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str' + } + + attribute_map = { + 'id': 'id' + } + + def __init__(self, id=None): # noqa: E501 + """SandboxAppInstances - a model defined in Swagger""" # noqa: E501 + self._id = None + self.discriminator = None + self.id = id + + @property + def id(self): + """Gets the id of this SandboxAppInstances. # noqa: E501 + + The application instance identifier. # noqa: E501 + + :return: The id of this SandboxAppInstances. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this SandboxAppInstances. + + The application instance identifier. # noqa: E501 + + :param id: The id of this SandboxAppInstances. # noqa: E501 + :type: str + """ + if id is None: + raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501 + + self._id = id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(SandboxAppInstances, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, SandboxAppInstances): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/sandbox_logs_subscriptions.py b/examples/demo9/python/mecapp/swagger_client/models/sandbox_logs_subscriptions.py new file mode 100644 index 0000000000000000000000000000000000000000..66b8cfde8e712fbac3f9073b6aeda01504ea6814 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/sandbox_logs_subscriptions.py @@ -0,0 +1,141 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class SandboxLogsSubscriptions(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'callback_reference': 'str', + 'subscription_reference': 'str' + } + + attribute_map = { + 'callback_reference': 'callbackReference', + 'subscription_reference': 'subscriptionReference' + } + + def __init__(self, callback_reference=None, subscription_reference=None): # noqa: E501 + """SandboxLogsSubscriptions - a model defined in Swagger""" # noqa: E501 + self._callback_reference = None + self._subscription_reference = None + self.discriminator = None + self.callback_reference = callback_reference + if subscription_reference is not None: + self.subscription_reference = subscription_reference + + @property + def callback_reference(self): + """Gets the callback_reference of this SandboxLogsSubscriptions. # noqa: E501 + + The callback to notify log messages. # noqa: E501 + + :return: The callback_reference of this SandboxLogsSubscriptions. # noqa: E501 + :rtype: str + """ + return self._callback_reference + + @callback_reference.setter + def callback_reference(self, callback_reference): + """Sets the callback_reference of this SandboxLogsSubscriptions. + + The callback to notify log messages. # noqa: E501 + + :param callback_reference: The callback_reference of this SandboxLogsSubscriptions. # noqa: E501 + :type: str + """ + if callback_reference is None: + raise ValueError("Invalid value for `callback_reference`, must not be `None`") # noqa: E501 + + self._callback_reference = callback_reference + + @property + def subscription_reference(self): + """Gets the subscription_reference of this SandboxLogsSubscriptions. # noqa: E501 + + The reference of the subscription. # noqa: E501 + + :return: The subscription_reference of this SandboxLogsSubscriptions. # noqa: E501 + :rtype: str + """ + return self._subscription_reference + + @subscription_reference.setter + def subscription_reference(self, subscription_reference): + """Sets the subscription_reference of this SandboxLogsSubscriptions. + + The reference of the subscription. # noqa: E501 + + :param subscription_reference: The subscription_reference of this SandboxLogsSubscriptions. # noqa: E501 + :type: str + """ + + self._subscription_reference = subscription_reference + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(SandboxLogsSubscriptions, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, SandboxLogsSubscriptions): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/sandbox_mec_services.py b/examples/demo9/python/mecapp/swagger_client/models/sandbox_mec_services.py new file mode 100644 index 0000000000000000000000000000000000000000..042140130be983042203899dba998f358e2a805c --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/sandbox_mec_services.py @@ -0,0 +1,141 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class SandboxMecServices(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'service_id': 'str' + } + + attribute_map = { + 'id': 'id', + 'service_id': 'service_id' + } + + def __init__(self, id=None, service_id=None): # noqa: E501 + """SandboxMecServices - a model defined in Swagger""" # noqa: E501 + self._id = None + self._service_id = None + self.discriminator = None + self.id = id + if service_id is not None: + self.service_id = service_id + + @property + def id(self): + """Gets the id of this SandboxMecServices. # noqa: E501 + + The MEC service name. # noqa: E501 + + :return: The id of this SandboxMecServices. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this SandboxMecServices. + + The MEC service name. # noqa: E501 + + :param id: The id of this SandboxMecServices. # noqa: E501 + :type: str + """ + if id is None: + raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501 + + self._id = id + + @property + def service_id(self): + """Gets the service_id of this SandboxMecServices. # noqa: E501 + + When a MEC service is selected, this field contains a token which shall be used in MEC service API URI. # noqa: E501 + + :return: The service_id of this SandboxMecServices. # noqa: E501 + :rtype: str + """ + return self._service_id + + @service_id.setter + def service_id(self, service_id): + """Sets the service_id of this SandboxMecServices. + + When a MEC service is selected, this field contains a token which shall be used in MEC service API URI. # noqa: E501 + + :param service_id: The service_id of this SandboxMecServices. # noqa: E501 + :type: str + """ + + self._service_id = service_id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(SandboxMecServices, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, SandboxMecServices): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/sandbox_network_scenario.py b/examples/demo9/python/mecapp/swagger_client/models/sandbox_network_scenario.py new file mode 100644 index 0000000000000000000000000000000000000000..333eb735dbf1a94b6db03743f3a77a69cf94a0b5 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/sandbox_network_scenario.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class SandboxNetworkScenario(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str' + } + + attribute_map = { + 'id': 'id' + } + + def __init__(self, id=None): # noqa: E501 + """SandboxNetworkScenario - a model defined in Swagger""" # noqa: E501 + self._id = None + self.discriminator = None + self.id = id + + @property + def id(self): + """Gets the id of this SandboxNetworkScenario. # noqa: E501 + + The network scenario name. # noqa: E501 + + :return: The id of this SandboxNetworkScenario. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this SandboxNetworkScenario. + + The network scenario name. # noqa: E501 + + :param id: The id of this SandboxNetworkScenario. # noqa: E501 + :type: str + """ + if id is None: + raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501 + + self._id = id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(SandboxNetworkScenario, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, SandboxNetworkScenario): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/scenario.py b/examples/demo9/python/mecapp/swagger_client/models/scenario.py new file mode 100644 index 0000000000000000000000000000000000000000..c4a9dcc2dfcf01ca3cdeec316b76fea2066b91a9 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/scenario.py @@ -0,0 +1,248 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Scenario(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'version': 'str', + 'id': 'str', + 'name': 'str', + 'description': 'str', + 'config': 'ScenarioConfig', + 'deployment': 'Deployment' + } + + attribute_map = { + 'version': 'version', + 'id': 'id', + 'name': 'name', + 'description': 'description', + 'config': 'config', + 'deployment': 'deployment' + } + + def __init__(self, version=None, id=None, name=None, description=None, config=None, deployment=None): # noqa: E501 + """Scenario - a model defined in Swagger""" # noqa: E501 + self._version = None + self._id = None + self._name = None + self._description = None + self._config = None + self._deployment = None + self.discriminator = None + if version is not None: + self.version = version + if id is not None: + self.id = id + if name is not None: + self.name = name + if description is not None: + self.description = description + if config is not None: + self.config = config + if deployment is not None: + self.deployment = deployment + + @property + def version(self): + """Gets the version of this Scenario. # noqa: E501 + + Scenario version # noqa: E501 + + :return: The version of this Scenario. # noqa: E501 + :rtype: str + """ + return self._version + + @version.setter + def version(self, version): + """Sets the version of this Scenario. + + Scenario version # noqa: E501 + + :param version: The version of this Scenario. # noqa: E501 + :type: str + """ + + self._version = version + + @property + def id(self): + """Gets the id of this Scenario. # noqa: E501 + + Unique scenario ID # noqa: E501 + + :return: The id of this Scenario. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Scenario. + + Unique scenario ID # noqa: E501 + + :param id: The id of this Scenario. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this Scenario. # noqa: E501 + + Unique scenario name # noqa: E501 + + :return: The name of this Scenario. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Scenario. + + Unique scenario name # noqa: E501 + + :param name: The name of this Scenario. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def description(self): + """Gets the description of this Scenario. # noqa: E501 + + User description of the scenario. # noqa: E501 + + :return: The description of this Scenario. # noqa: E501 + :rtype: str + """ + return self._description + + @description.setter + def description(self, description): + """Sets the description of this Scenario. + + User description of the scenario. # noqa: E501 + + :param description: The description of this Scenario. # noqa: E501 + :type: str + """ + + self._description = description + + @property + def config(self): + """Gets the config of this Scenario. # noqa: E501 + + + :return: The config of this Scenario. # noqa: E501 + :rtype: ScenarioConfig + """ + return self._config + + @config.setter + def config(self, config): + """Sets the config of this Scenario. + + + :param config: The config of this Scenario. # noqa: E501 + :type: ScenarioConfig + """ + + self._config = config + + @property + def deployment(self): + """Gets the deployment of this Scenario. # noqa: E501 + + + :return: The deployment of this Scenario. # noqa: E501 + :rtype: Deployment + """ + return self._deployment + + @deployment.setter + def deployment(self, deployment): + """Sets the deployment of this Scenario. + + + :param deployment: The deployment of this Scenario. # noqa: E501 + :type: Deployment + """ + + self._deployment = deployment + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Scenario, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Scenario): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/scenario_config.py b/examples/demo9/python/mecapp/swagger_client/models/scenario_config.py new file mode 100644 index 0000000000000000000000000000000000000000..126dad37e44b7e05ccbf709c11f0740158e79b24 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/scenario_config.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ScenarioConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'visualization': 'str', + 'other': 'str' + } + + attribute_map = { + 'visualization': 'visualization', + 'other': 'other' + } + + def __init__(self, visualization=None, other=None): # noqa: E501 + """ScenarioConfig - a model defined in Swagger""" # noqa: E501 + self._visualization = None + self._other = None + self.discriminator = None + if visualization is not None: + self.visualization = visualization + if other is not None: + self.other = other + + @property + def visualization(self): + """Gets the visualization of this ScenarioConfig. # noqa: E501 + + Visualization configuration # noqa: E501 + + :return: The visualization of this ScenarioConfig. # noqa: E501 + :rtype: str + """ + return self._visualization + + @visualization.setter + def visualization(self, visualization): + """Sets the visualization of this ScenarioConfig. + + Visualization configuration # noqa: E501 + + :param visualization: The visualization of this ScenarioConfig. # noqa: E501 + :type: str + """ + + self._visualization = visualization + + @property + def other(self): + """Gets the other of this ScenarioConfig. # noqa: E501 + + Other scenario configuration # noqa: E501 + + :return: The other of this ScenarioConfig. # noqa: E501 + :rtype: str + """ + return self._other + + @other.setter + def other(self, other): + """Sets the other of this ScenarioConfig. + + Other scenario configuration # noqa: E501 + + :param other: The other of this ScenarioConfig. # noqa: E501 + :type: str + """ + + self._other = other + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ScenarioConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ScenarioConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/service_config.py b/examples/demo9/python/mecapp/swagger_client/models/service_config.py new file mode 100644 index 0000000000000000000000000000000000000000..65b7a36974ef5d454a6a60eb47147e985b3a5eb3 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/service_config.py @@ -0,0 +1,166 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ServiceConfig(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'name': 'str', + 'me_svc_name': 'str', + 'ports': 'list[ServicePort]' + } + + attribute_map = { + 'name': 'name', + 'me_svc_name': 'meSvcName', + 'ports': 'ports' + } + + def __init__(self, name=None, me_svc_name=None, ports=None): # noqa: E501 + """ServiceConfig - a model defined in Swagger""" # noqa: E501 + self._name = None + self._me_svc_name = None + self._ports = None + self.discriminator = None + if name is not None: + self.name = name + if me_svc_name is not None: + self.me_svc_name = me_svc_name + if ports is not None: + self.ports = ports + + @property + def name(self): + """Gets the name of this ServiceConfig. # noqa: E501 + + Unique service name # noqa: E501 + + :return: The name of this ServiceConfig. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this ServiceConfig. + + Unique service name # noqa: E501 + + :param name: The name of this ServiceConfig. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def me_svc_name(self): + """Gets the me_svc_name of this ServiceConfig. # noqa: E501 + + Multi-Edge service name, if any # noqa: E501 + + :return: The me_svc_name of this ServiceConfig. # noqa: E501 + :rtype: str + """ + return self._me_svc_name + + @me_svc_name.setter + def me_svc_name(self, me_svc_name): + """Sets the me_svc_name of this ServiceConfig. + + Multi-Edge service name, if any # noqa: E501 + + :param me_svc_name: The me_svc_name of this ServiceConfig. # noqa: E501 + :type: str + """ + + self._me_svc_name = me_svc_name + + @property + def ports(self): + """Gets the ports of this ServiceConfig. # noqa: E501 + + + :return: The ports of this ServiceConfig. # noqa: E501 + :rtype: list[ServicePort] + """ + return self._ports + + @ports.setter + def ports(self, ports): + """Sets the ports of this ServiceConfig. + + + :param ports: The ports of this ServiceConfig. # noqa: E501 + :type: list[ServicePort] + """ + + self._ports = ports + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ServiceConfig, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ServiceConfig): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/service_port.py b/examples/demo9/python/mecapp/swagger_client/models/service_port.py new file mode 100644 index 0000000000000000000000000000000000000000..6b7d34e707f7ed95a511d9566dcefbb37a8c6617 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/service_port.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class ServicePort(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'protocol': 'str', + 'port': 'int', + 'external_port': 'int' + } + + attribute_map = { + 'protocol': 'protocol', + 'port': 'port', + 'external_port': 'externalPort' + } + + def __init__(self, protocol=None, port=None, external_port=None): # noqa: E501 + """ServicePort - a model defined in Swagger""" # noqa: E501 + self._protocol = None + self._port = None + self._external_port = None + self.discriminator = None + if protocol is not None: + self.protocol = protocol + if port is not None: + self.port = port + if external_port is not None: + self.external_port = external_port + + @property + def protocol(self): + """Gets the protocol of this ServicePort. # noqa: E501 + + Protocol that the application is using (TCP or UDP) # noqa: E501 + + :return: The protocol of this ServicePort. # noqa: E501 + :rtype: str + """ + return self._protocol + + @protocol.setter + def protocol(self, protocol): + """Sets the protocol of this ServicePort. + + Protocol that the application is using (TCP or UDP) # noqa: E501 + + :param protocol: The protocol of this ServicePort. # noqa: E501 + :type: str + """ + + self._protocol = protocol + + @property + def port(self): + """Gets the port of this ServicePort. # noqa: E501 + + Port number that the service is listening on # noqa: E501 + + :return: The port of this ServicePort. # noqa: E501 + :rtype: int + """ + return self._port + + @port.setter + def port(self, port): + """Sets the port of this ServicePort. + + Port number that the service is listening on # noqa: E501 + + :param port: The port of this ServicePort. # noqa: E501 + :type: int + """ + + self._port = port + + @property + def external_port(self): + """Gets the external_port of this ServicePort. # noqa: E501 + + External port number on which to expose the application (30000 - 32767)
  • Only one application allowed per external port
  • Scenario builder must configure to prevent conflicts # noqa: E501 + + :return: The external_port of this ServicePort. # noqa: E501 + :rtype: int + """ + return self._external_port + + @external_port.setter + def external_port(self, external_port): + """Sets the external_port of this ServicePort. + + External port number on which to expose the application (30000 - 32767)
  • Only one application allowed per external port
  • Scenario builder must configure to prevent conflicts # noqa: E501 + + :param external_port: The external_port of this ServicePort. # noqa: E501 + :type: int + """ + + self._external_port = external_port + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(ServicePort, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ServicePort): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/ue.py b/examples/demo9/python/mecapp/swagger_client/models/ue.py new file mode 100644 index 0000000000000000000000000000000000000000..d27f1f3e61c4bf96e30acd89eec1a10ed3f72bf2 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/ue.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class UE(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str' + } + + attribute_map = { + 'id': 'id' + } + + def __init__(self, id=None): # noqa: E501 + """UE - a model defined in Swagger""" # noqa: E501 + self._id = None + self.discriminator = None + self.id = id + + @property + def id(self): + """Gets the id of this UE. # noqa: E501 + + The UE name. # noqa: E501 + + :return: The id of this UE. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this UE. + + The UE name. # noqa: E501 + + :param id: The id of this UE. # noqa: E501 + :type: str + """ + if id is None: + raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501 + + self._id = id + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(UE, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, UE): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/models/zone.py b/examples/demo9/python/mecapp/swagger_client/models/zone.py new file mode 100644 index 0000000000000000000000000000000000000000..c7b2dd02d4785394af36c22f1449a5230d94e879 --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/models/zone.py @@ -0,0 +1,618 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +import pprint +import re # noqa: F401 + +import six + +class Zone(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { + 'id': 'str', + 'name': 'str', + 'type': 'str', + 'net_char': 'NetworkCharacteristics', + 'inter_fog_latency': 'int', + 'inter_fog_latency_variation': 'int', + 'inter_fog_throughput': 'int', + 'inter_fog_packet_loss': 'float', + 'inter_edge_latency': 'int', + 'inter_edge_latency_variation': 'int', + 'inter_edge_throughput': 'int', + 'inter_edge_packet_loss': 'float', + 'edge_fog_latency': 'int', + 'edge_fog_latency_variation': 'int', + 'edge_fog_throughput': 'int', + 'edge_fog_packet_loss': 'float', + 'meta': 'dict(str, str)', + 'user_meta': 'dict(str, str)', + 'network_locations': 'list[NetworkLocation]' + } + + attribute_map = { + 'id': 'id', + 'name': 'name', + 'type': 'type', + 'net_char': 'netChar', + 'inter_fog_latency': 'interFogLatency', + 'inter_fog_latency_variation': 'interFogLatencyVariation', + 'inter_fog_throughput': 'interFogThroughput', + 'inter_fog_packet_loss': 'interFogPacketLoss', + 'inter_edge_latency': 'interEdgeLatency', + 'inter_edge_latency_variation': 'interEdgeLatencyVariation', + 'inter_edge_throughput': 'interEdgeThroughput', + 'inter_edge_packet_loss': 'interEdgePacketLoss', + 'edge_fog_latency': 'edgeFogLatency', + 'edge_fog_latency_variation': 'edgeFogLatencyVariation', + 'edge_fog_throughput': 'edgeFogThroughput', + 'edge_fog_packet_loss': 'edgeFogPacketLoss', + 'meta': 'meta', + 'user_meta': 'userMeta', + 'network_locations': 'networkLocations' + } + + def __init__(self, id=None, name=None, type=None, net_char=None, inter_fog_latency=None, inter_fog_latency_variation=None, inter_fog_throughput=None, inter_fog_packet_loss=None, inter_edge_latency=None, inter_edge_latency_variation=None, inter_edge_throughput=None, inter_edge_packet_loss=None, edge_fog_latency=None, edge_fog_latency_variation=None, edge_fog_throughput=None, edge_fog_packet_loss=None, meta=None, user_meta=None, network_locations=None): # noqa: E501 + """Zone - a model defined in Swagger""" # noqa: E501 + self._id = None + self._name = None + self._type = None + self._net_char = None + self._inter_fog_latency = None + self._inter_fog_latency_variation = None + self._inter_fog_throughput = None + self._inter_fog_packet_loss = None + self._inter_edge_latency = None + self._inter_edge_latency_variation = None + self._inter_edge_throughput = None + self._inter_edge_packet_loss = None + self._edge_fog_latency = None + self._edge_fog_latency_variation = None + self._edge_fog_throughput = None + self._edge_fog_packet_loss = None + self._meta = None + self._user_meta = None + self._network_locations = None + self.discriminator = None + if id is not None: + self.id = id + if name is not None: + self.name = name + if type is not None: + self.type = type + if net_char is not None: + self.net_char = net_char + if inter_fog_latency is not None: + self.inter_fog_latency = inter_fog_latency + if inter_fog_latency_variation is not None: + self.inter_fog_latency_variation = inter_fog_latency_variation + if inter_fog_throughput is not None: + self.inter_fog_throughput = inter_fog_throughput + if inter_fog_packet_loss is not None: + self.inter_fog_packet_loss = inter_fog_packet_loss + if inter_edge_latency is not None: + self.inter_edge_latency = inter_edge_latency + if inter_edge_latency_variation is not None: + self.inter_edge_latency_variation = inter_edge_latency_variation + if inter_edge_throughput is not None: + self.inter_edge_throughput = inter_edge_throughput + if inter_edge_packet_loss is not None: + self.inter_edge_packet_loss = inter_edge_packet_loss + if edge_fog_latency is not None: + self.edge_fog_latency = edge_fog_latency + if edge_fog_latency_variation is not None: + self.edge_fog_latency_variation = edge_fog_latency_variation + if edge_fog_throughput is not None: + self.edge_fog_throughput = edge_fog_throughput + if edge_fog_packet_loss is not None: + self.edge_fog_packet_loss = edge_fog_packet_loss + if meta is not None: + self.meta = meta + if user_meta is not None: + self.user_meta = user_meta + if network_locations is not None: + self.network_locations = network_locations + + @property + def id(self): + """Gets the id of this Zone. # noqa: E501 + + Unique zone ID # noqa: E501 + + :return: The id of this Zone. # noqa: E501 + :rtype: str + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Zone. + + Unique zone ID # noqa: E501 + + :param id: The id of this Zone. # noqa: E501 + :type: str + """ + + self._id = id + + @property + def name(self): + """Gets the name of this Zone. # noqa: E501 + + Zone name # noqa: E501 + + :return: The name of this Zone. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Zone. + + Zone name # noqa: E501 + + :param name: The name of this Zone. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def type(self): + """Gets the type of this Zone. # noqa: E501 + + Zone type # noqa: E501 + + :return: The type of this Zone. # noqa: E501 + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this Zone. + + Zone type # noqa: E501 + + :param type: The type of this Zone. # noqa: E501 + :type: str + """ + allowed_values = ["ZONE", "COMMON"] # noqa: E501 + if type not in allowed_values: + raise ValueError( + "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + .format(type, allowed_values) + ) + + self._type = type + + @property + def net_char(self): + """Gets the net_char of this Zone. # noqa: E501 + + + :return: The net_char of this Zone. # noqa: E501 + :rtype: NetworkCharacteristics + """ + return self._net_char + + @net_char.setter + def net_char(self, net_char): + """Sets the net_char of this Zone. + + + :param net_char: The net_char of this Zone. # noqa: E501 + :type: NetworkCharacteristics + """ + + self._net_char = net_char + + @property + def inter_fog_latency(self): + """Gets the inter_fog_latency of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_fog_latency of this Zone. # noqa: E501 + :rtype: int + """ + return self._inter_fog_latency + + @inter_fog_latency.setter + def inter_fog_latency(self, inter_fog_latency): + """Sets the inter_fog_latency of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_fog_latency: The inter_fog_latency of this Zone. # noqa: E501 + :type: int + """ + + self._inter_fog_latency = inter_fog_latency + + @property + def inter_fog_latency_variation(self): + """Gets the inter_fog_latency_variation of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_fog_latency_variation of this Zone. # noqa: E501 + :rtype: int + """ + return self._inter_fog_latency_variation + + @inter_fog_latency_variation.setter + def inter_fog_latency_variation(self, inter_fog_latency_variation): + """Sets the inter_fog_latency_variation of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_fog_latency_variation: The inter_fog_latency_variation of this Zone. # noqa: E501 + :type: int + """ + + self._inter_fog_latency_variation = inter_fog_latency_variation + + @property + def inter_fog_throughput(self): + """Gets the inter_fog_throughput of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_fog_throughput of this Zone. # noqa: E501 + :rtype: int + """ + return self._inter_fog_throughput + + @inter_fog_throughput.setter + def inter_fog_throughput(self, inter_fog_throughput): + """Sets the inter_fog_throughput of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_fog_throughput: The inter_fog_throughput of this Zone. # noqa: E501 + :type: int + """ + + self._inter_fog_throughput = inter_fog_throughput + + @property + def inter_fog_packet_loss(self): + """Gets the inter_fog_packet_loss of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_fog_packet_loss of this Zone. # noqa: E501 + :rtype: float + """ + return self._inter_fog_packet_loss + + @inter_fog_packet_loss.setter + def inter_fog_packet_loss(self, inter_fog_packet_loss): + """Sets the inter_fog_packet_loss of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_fog_packet_loss: The inter_fog_packet_loss of this Zone. # noqa: E501 + :type: float + """ + + self._inter_fog_packet_loss = inter_fog_packet_loss + + @property + def inter_edge_latency(self): + """Gets the inter_edge_latency of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_edge_latency of this Zone. # noqa: E501 + :rtype: int + """ + return self._inter_edge_latency + + @inter_edge_latency.setter + def inter_edge_latency(self, inter_edge_latency): + """Sets the inter_edge_latency of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_edge_latency: The inter_edge_latency of this Zone. # noqa: E501 + :type: int + """ + + self._inter_edge_latency = inter_edge_latency + + @property + def inter_edge_latency_variation(self): + """Gets the inter_edge_latency_variation of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_edge_latency_variation of this Zone. # noqa: E501 + :rtype: int + """ + return self._inter_edge_latency_variation + + @inter_edge_latency_variation.setter + def inter_edge_latency_variation(self, inter_edge_latency_variation): + """Sets the inter_edge_latency_variation of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_edge_latency_variation: The inter_edge_latency_variation of this Zone. # noqa: E501 + :type: int + """ + + self._inter_edge_latency_variation = inter_edge_latency_variation + + @property + def inter_edge_throughput(self): + """Gets the inter_edge_throughput of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_edge_throughput of this Zone. # noqa: E501 + :rtype: int + """ + return self._inter_edge_throughput + + @inter_edge_throughput.setter + def inter_edge_throughput(self, inter_edge_throughput): + """Sets the inter_edge_throughput of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_edge_throughput: The inter_edge_throughput of this Zone. # noqa: E501 + :type: int + """ + + self._inter_edge_throughput = inter_edge_throughput + + @property + def inter_edge_packet_loss(self): + """Gets the inter_edge_packet_loss of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :return: The inter_edge_packet_loss of this Zone. # noqa: E501 + :rtype: float + """ + return self._inter_edge_packet_loss + + @inter_edge_packet_loss.setter + def inter_edge_packet_loss(self, inter_edge_packet_loss): + """Sets the inter_edge_packet_loss of this Zone. + + **DEPRECATED** As of release 1.3.0, no longer supported # noqa: E501 + + :param inter_edge_packet_loss: The inter_edge_packet_loss of this Zone. # noqa: E501 + :type: float + """ + + self._inter_edge_packet_loss = inter_edge_packet_loss + + @property + def edge_fog_latency(self): + """Gets the edge_fog_latency of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, replaced by netChar latency # noqa: E501 + + :return: The edge_fog_latency of this Zone. # noqa: E501 + :rtype: int + """ + return self._edge_fog_latency + + @edge_fog_latency.setter + def edge_fog_latency(self, edge_fog_latency): + """Sets the edge_fog_latency of this Zone. + + **DEPRECATED** As of release 1.3.0, replaced by netChar latency # noqa: E501 + + :param edge_fog_latency: The edge_fog_latency of this Zone. # noqa: E501 + :type: int + """ + + self._edge_fog_latency = edge_fog_latency + + @property + def edge_fog_latency_variation(self): + """Gets the edge_fog_latency_variation of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, replaced by netChar latencyVariation # noqa: E501 + + :return: The edge_fog_latency_variation of this Zone. # noqa: E501 + :rtype: int + """ + return self._edge_fog_latency_variation + + @edge_fog_latency_variation.setter + def edge_fog_latency_variation(self, edge_fog_latency_variation): + """Sets the edge_fog_latency_variation of this Zone. + + **DEPRECATED** As of release 1.3.0, replaced by netChar latencyVariation # noqa: E501 + + :param edge_fog_latency_variation: The edge_fog_latency_variation of this Zone. # noqa: E501 + :type: int + """ + + self._edge_fog_latency_variation = edge_fog_latency_variation + + @property + def edge_fog_throughput(self): + """Gets the edge_fog_throughput of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, replaced by netChar throughput # noqa: E501 + + :return: The edge_fog_throughput of this Zone. # noqa: E501 + :rtype: int + """ + return self._edge_fog_throughput + + @edge_fog_throughput.setter + def edge_fog_throughput(self, edge_fog_throughput): + """Sets the edge_fog_throughput of this Zone. + + **DEPRECATED** As of release 1.3.0, replaced by netChar throughput # noqa: E501 + + :param edge_fog_throughput: The edge_fog_throughput of this Zone. # noqa: E501 + :type: int + """ + + self._edge_fog_throughput = edge_fog_throughput + + @property + def edge_fog_packet_loss(self): + """Gets the edge_fog_packet_loss of this Zone. # noqa: E501 + + **DEPRECATED** As of release 1.3.0, replaced by netChar packetLoss # noqa: E501 + + :return: The edge_fog_packet_loss of this Zone. # noqa: E501 + :rtype: float + """ + return self._edge_fog_packet_loss + + @edge_fog_packet_loss.setter + def edge_fog_packet_loss(self, edge_fog_packet_loss): + """Sets the edge_fog_packet_loss of this Zone. + + **DEPRECATED** As of release 1.3.0, replaced by netChar packetLoss # noqa: E501 + + :param edge_fog_packet_loss: The edge_fog_packet_loss of this Zone. # noqa: E501 + :type: float + """ + + self._edge_fog_packet_loss = edge_fog_packet_loss + + @property + def meta(self): + """Gets the meta of this Zone. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The meta of this Zone. # noqa: E501 + :rtype: dict(str, str) + """ + return self._meta + + @meta.setter + def meta(self, meta): + """Sets the meta of this Zone. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param meta: The meta of this Zone. # noqa: E501 + :type: dict(str, str) + """ + + self._meta = meta + + @property + def user_meta(self): + """Gets the user_meta of this Zone. # noqa: E501 + + Key/Value Pair Map (string, string) # noqa: E501 + + :return: The user_meta of this Zone. # noqa: E501 + :rtype: dict(str, str) + """ + return self._user_meta + + @user_meta.setter + def user_meta(self, user_meta): + """Sets the user_meta of this Zone. + + Key/Value Pair Map (string, string) # noqa: E501 + + :param user_meta: The user_meta of this Zone. # noqa: E501 + :type: dict(str, str) + """ + + self._user_meta = user_meta + + @property + def network_locations(self): + """Gets the network_locations of this Zone. # noqa: E501 + + + :return: The network_locations of this Zone. # noqa: E501 + :rtype: list[NetworkLocation] + """ + return self._network_locations + + @network_locations.setter + def network_locations(self, network_locations): + """Sets the network_locations of this Zone. + + + :param network_locations: The network_locations of this Zone. # noqa: E501 + :type: list[NetworkLocation] + """ + + self._network_locations = network_locations + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + if issubclass(Zone, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Zone): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/examples/demo9/python/mecapp/swagger_client/rest.py b/examples/demo9/python/mecapp/swagger_client/rest.py new file mode 100644 index 0000000000000000000000000000000000000000..8a076b4e73f94e888978fc70b293fc9df895458b --- /dev/null +++ b/examples/demo9/python/mecapp/swagger_client/rest.py @@ -0,0 +1,317 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.9 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import io +import json +import logging +import re +import ssl + +import certifi +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import urlencode + +try: + import urllib3 +except ImportError: + raise ImportError('Swagger python client requires urllib3.') + + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.urllib3_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = resp.data + + def getheaders(self): + """Returns a dictionary of the response headers.""" + return self.urllib3_response.getheaders() + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.urllib3_response.getheader(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + + if maxsize is None: + if configuration.connection_pool_maxsize is not None: + maxsize = configuration.connection_pool_maxsize + else: + maxsize = 4 + + # https pool manager + if configuration.proxy: + self.pool_manager = urllib3.ProxyManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Perform requests. + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, ) if six.PY3 else (int, long)): # noqa: E501,F821 + timeout = urllib3.Timeout(total=_request_timeout) + elif (isinstance(_request_timeout, tuple) and + len(_request_timeout) == 2): + timeout = urllib3.Timeout( + connect=_request_timeout[0], read=_request_timeout[1]) + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if query_params: + url += '?' + urlencode(query_params) + if re.search('json', headers['Content-Type'], re.IGNORECASE): + request_body = '{}' + if body is not None: + request_body = json.dumps(body) + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=False, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=True, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + # Pass a `string` parameter directly in the body to support + # other content types than Json when `body` argument is + # provided in serialized form + elif isinstance(body, str): + request_body = body + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request(method, url, + fields=query_params, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + except urllib3.exceptions.SSLError as e: + msg = "{0}\n{1}".format(type(e).__name__, str(e)) + raise ApiException(status=0, reason=msg) + + if _preload_content: + r = RESTResponse(r) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\n"\ + "Reason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/examples/demo9/python/mecapp/test-requirements.txt b/examples/demo9/python/mecapp/test-requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2702246c0e6f92a1c41c0960879e07da339e4b7d --- /dev/null +++ b/examples/demo9/python/mecapp/test-requirements.txt @@ -0,0 +1,5 @@ +coverage>=4.0.3 +nose>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 diff --git a/examples/demo9/python/mecapp/test/__init__.py b/examples/demo9/python/mecapp/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..576f56f87e791f287237daf5dcbe03fa38f62b1e --- /dev/null +++ b/examples/demo9/python/mecapp/test/__init__.py @@ -0,0 +1 @@ +# coding: utf-8 \ No newline at end of file diff --git a/examples/demo9/python/mecapp/test/test_application_info.py b/examples/demo9/python/mecapp/test/test_application_info.py new file mode 100644 index 0000000000000000000000000000000000000000..e8f2df4857fa5a9e0c16dc3a9c4c1ea627f09f86 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_application_info.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.application_info import ApplicationInfo # noqa: E501 +from swagger_client.rest import ApiException + + +class TestApplicationInfo(unittest.TestCase): + """ApplicationInfo unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testApplicationInfo(self): + """Test ApplicationInfo""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.application_info.ApplicationInfo() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_authorization_api.py b/examples/demo9/python/mecapp/test/test_authorization_api.py new file mode 100644 index 0000000000000000000000000000000000000000..d64a7a5d1377f520d12a93c9fdf52beb20f51039 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_authorization_api.py @@ -0,0 +1,47 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.api.authorization_api import AuthorizationApi # noqa: E501 +from swagger_client.rest import ApiException + + +class TestAuthorizationApi(unittest.TestCase): + """AuthorizationApi unit test stubs""" + + def setUp(self): + self.api = AuthorizationApi() # noqa: E501 + + def tearDown(self): + pass + + def test_login(self): + """Test case for login + + Initiate OAuth login procedure and creates a MEC Sandbox instance # noqa: E501 + """ + pass + + def test_logout(self): + """Test case for logout + + Terminates User Session and delete the Sandbox instance # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_cellular_domain_config.py b/examples/demo9/python/mecapp/test/test_cellular_domain_config.py new file mode 100644 index 0000000000000000000000000000000000000000..c849fa09c572b32d9d0fc4401e647268a9d1cb1e --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_cellular_domain_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.cellular_domain_config import CellularDomainConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestCellularDomainConfig(unittest.TestCase): + """CellularDomainConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testCellularDomainConfig(self): + """Test CellularDomainConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.cellular_domain_config.CellularDomainConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_cellular_poa_config.py b/examples/demo9/python/mecapp/test/test_cellular_poa_config.py new file mode 100644 index 0000000000000000000000000000000000000000..df61b5883b0a8f56236c525f843e9d46e62289a3 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_cellular_poa_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.cellular_poa_config import CellularPoaConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestCellularPoaConfig(unittest.TestCase): + """CellularPoaConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testCellularPoaConfig(self): + """Test CellularPoaConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.cellular_poa_config.CellularPoaConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_connectivity_config.py b/examples/demo9/python/mecapp/test/test_connectivity_config.py new file mode 100644 index 0000000000000000000000000000000000000000..d1f3600c9a431100fa1077e92af0ae2e95d9581f --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_connectivity_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.connectivity_config import ConnectivityConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestConnectivityConfig(unittest.TestCase): + """ConnectivityConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testConnectivityConfig(self): + """Test ConnectivityConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.connectivity_config.ConnectivityConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_cpu_config.py b/examples/demo9/python/mecapp/test/test_cpu_config.py new file mode 100644 index 0000000000000000000000000000000000000000..67ad944a68e6933e1cfbb8e7830fa3bb79fe5dc6 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_cpu_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.cpu_config import CpuConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestCpuConfig(unittest.TestCase): + """CpuConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testCpuConfig(self): + """Test CpuConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.cpu_config.CpuConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_d2d_config.py b/examples/demo9/python/mecapp/test/test_d2d_config.py new file mode 100644 index 0000000000000000000000000000000000000000..678cc5e6d8eb0dd094f3da49cfce6a1511cc5eaf --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_d2d_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.d2d_config import D2dConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestD2dConfig(unittest.TestCase): + """D2dConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testD2dConfig(self): + """Test D2dConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.d2d_config.D2dConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_deployment.py b/examples/demo9/python/mecapp/test/test_deployment.py new file mode 100644 index 0000000000000000000000000000000000000000..e34d3f391a9e8329a8eb8115230f293f315f358d --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_deployment.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.deployment import Deployment # noqa: E501 +from swagger_client.rest import ApiException + + +class TestDeployment(unittest.TestCase): + """Deployment unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testDeployment(self): + """Test Deployment""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.deployment.Deployment() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_dn_config.py b/examples/demo9/python/mecapp/test/test_dn_config.py new file mode 100644 index 0000000000000000000000000000000000000000..cdfafdfeee71706c4f4e10433239a60a5ab47e35 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_dn_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.dn_config import DNConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestDNConfig(unittest.TestCase): + """DNConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testDNConfig(self): + """Test DNConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.dn_config.DNConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_domain.py b/examples/demo9/python/mecapp/test/test_domain.py new file mode 100644 index 0000000000000000000000000000000000000000..556330859bd6f992febac184b60324e876d87292 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_domain.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.domain import Domain # noqa: E501 +from swagger_client.rest import ApiException + + +class TestDomain(unittest.TestCase): + """Domain unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testDomain(self): + """Test Domain""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.domain.Domain() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_egress_service.py b/examples/demo9/python/mecapp/test/test_egress_service.py new file mode 100644 index 0000000000000000000000000000000000000000..caf8d8211d55982890eff1615e5c7f1d7e0bf587 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_egress_service.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.egress_service import EgressService # noqa: E501 +from swagger_client.rest import ApiException + + +class TestEgressService(unittest.TestCase): + """EgressService unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testEgressService(self): + """Test EgressService""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.egress_service.EgressService() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_external_config.py b/examples/demo9/python/mecapp/test/test_external_config.py new file mode 100644 index 0000000000000000000000000000000000000000..35b7cd7f81c53860aebb839deffc0cbad7e613d6 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_external_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.external_config import ExternalConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestExternalConfig(unittest.TestCase): + """ExternalConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testExternalConfig(self): + """Test ExternalConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.external_config.ExternalConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_geo_data.py b/examples/demo9/python/mecapp/test/test_geo_data.py new file mode 100644 index 0000000000000000000000000000000000000000..c82ebe27cb306206e8fafd71235344d216aaf8d3 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_geo_data.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.geo_data import GeoData # noqa: E501 +from swagger_client.rest import ApiException + + +class TestGeoData(unittest.TestCase): + """GeoData unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testGeoData(self): + """Test GeoData""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.geo_data.GeoData() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_gpu_config.py b/examples/demo9/python/mecapp/test/test_gpu_config.py new file mode 100644 index 0000000000000000000000000000000000000000..6dffcd9311d7f98712b8dc07e13c6cf8de938938 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_gpu_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.gpu_config import GpuConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestGpuConfig(unittest.TestCase): + """GpuConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testGpuConfig(self): + """Test GpuConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.gpu_config.GpuConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_ingress_service.py b/examples/demo9/python/mecapp/test/test_ingress_service.py new file mode 100644 index 0000000000000000000000000000000000000000..8e3a8bb6efed8bf8adc7b5c368ae0632a193bbb1 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_ingress_service.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.ingress_service import IngressService # noqa: E501 +from swagger_client.rest import ApiException + + +class TestIngressService(unittest.TestCase): + """IngressService unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testIngressService(self): + """Test IngressService""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.ingress_service.IngressService() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_line_string.py b/examples/demo9/python/mecapp/test/test_line_string.py new file mode 100644 index 0000000000000000000000000000000000000000..cebe7822466e363f089d70e4716ce0665799a341 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_line_string.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.line_string import LineString # noqa: E501 +from swagger_client.rest import ApiException + + +class TestLineString(unittest.TestCase): + """LineString unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testLineString(self): + """Test LineString""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.line_string.LineString() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_memory_config.py b/examples/demo9/python/mecapp/test/test_memory_config.py new file mode 100644 index 0000000000000000000000000000000000000000..d260eef6b7ff433b5948f4aab8e33a8e7180e751 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_memory_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.memory_config import MemoryConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestMemoryConfig(unittest.TestCase): + """MemoryConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testMemoryConfig(self): + """Test MemoryConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.memory_config.MemoryConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_network_characteristics.py b/examples/demo9/python/mecapp/test/test_network_characteristics.py new file mode 100644 index 0000000000000000000000000000000000000000..aaa23a112e5b8775775812b075b833853679ddc5 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_network_characteristics.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.network_characteristics import NetworkCharacteristics # noqa: E501 +from swagger_client.rest import ApiException + + +class TestNetworkCharacteristics(unittest.TestCase): + """NetworkCharacteristics unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testNetworkCharacteristics(self): + """Test NetworkCharacteristics""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.network_characteristics.NetworkCharacteristics() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_network_location.py b/examples/demo9/python/mecapp/test/test_network_location.py new file mode 100644 index 0000000000000000000000000000000000000000..7a19b3bcc38a879a12ef4cac72023ec0c077af85 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_network_location.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.network_location import NetworkLocation # noqa: E501 +from swagger_client.rest import ApiException + + +class TestNetworkLocation(unittest.TestCase): + """NetworkLocation unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testNetworkLocation(self): + """Test NetworkLocation""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.network_location.NetworkLocation() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_physical_location.py b/examples/demo9/python/mecapp/test/test_physical_location.py new file mode 100644 index 0000000000000000000000000000000000000000..f9d4a2dec2c113983a8a2b96c8878da6e02231d6 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_physical_location.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.physical_location import PhysicalLocation # noqa: E501 +from swagger_client.rest import ApiException + + +class TestPhysicalLocation(unittest.TestCase): + """PhysicalLocation unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPhysicalLocation(self): + """Test PhysicalLocation""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.physical_location.PhysicalLocation() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_poa4_g_config.py b/examples/demo9/python/mecapp/test/test_poa4_g_config.py new file mode 100644 index 0000000000000000000000000000000000000000..d41ef94e651ed69a54857a3493f8ef82a90b2af9 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_poa4_g_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.poa4_g_config import Poa4GConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestPoa4GConfig(unittest.TestCase): + """Poa4GConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPoa4GConfig(self): + """Test Poa4GConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.poa4_g_config.Poa4GConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_poa5_g_config.py b/examples/demo9/python/mecapp/test/test_poa5_g_config.py new file mode 100644 index 0000000000000000000000000000000000000000..d5f6f2fe55773f2858818bfd7a804336ec7c1af6 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_poa5_g_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.poa5_g_config import Poa5GConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestPoa5GConfig(unittest.TestCase): + """Poa5GConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPoa5GConfig(self): + """Test Poa5GConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.poa5_g_config.Poa5GConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_poa_wifi_config.py b/examples/demo9/python/mecapp/test/test_poa_wifi_config.py new file mode 100644 index 0000000000000000000000000000000000000000..5a594ba0dc5d8985fe6ba5684d5270b7c43d9a8c --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_poa_wifi_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.poa_wifi_config import PoaWifiConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestPoaWifiConfig(unittest.TestCase): + """PoaWifiConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPoaWifiConfig(self): + """Test PoaWifiConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.poa_wifi_config.PoaWifiConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_point.py b/examples/demo9/python/mecapp/test/test_point.py new file mode 100644 index 0000000000000000000000000000000000000000..91b69a5c4d8cb0ef4f63b66a821380ab1f7eceab --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_point.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.point import Point # noqa: E501 +from swagger_client.rest import ApiException + + +class TestPoint(unittest.TestCase): + """Point unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPoint(self): + """Test Point""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.point.Point() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_problem_details.py b/examples/demo9/python/mecapp/test/test_problem_details.py new file mode 100644 index 0000000000000000000000000000000000000000..8f0e1e962ac44557500d315e3344bd01040aaef5 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_problem_details.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.problem_details import ProblemDetails # noqa: E501 +from swagger_client.rest import ApiException + + +class TestProblemDetails(unittest.TestCase): + """ProblemDetails unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testProblemDetails(self): + """Test ProblemDetails""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.problem_details.ProblemDetails() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_process.py b/examples/demo9/python/mecapp/test/test_process.py new file mode 100644 index 0000000000000000000000000000000000000000..4fc3188f11a3a37fbac1c387cceb0dd9a18acaf8 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_process.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.process import Process # noqa: E501 +from swagger_client.rest import ApiException + + +class TestProcess(unittest.TestCase): + """Process unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testProcess(self): + """Test Process""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.process.Process() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox.py b/examples/demo9/python/mecapp/test/test_sandbox.py new file mode 100644 index 0000000000000000000000000000000000000000..868d49d6af1d6a40866afe03a363eb78a3407f3b --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.sandbox import Sandbox # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandbox(unittest.TestCase): + """Sandbox unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testSandbox(self): + """Test Sandbox""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.sandbox.Sandbox() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_app_instances.py b/examples/demo9/python/mecapp/test/test_sandbox_app_instances.py new file mode 100644 index 0000000000000000000000000000000000000000..7a245a5d84dee2a129d0b17f76f932c1091615d9 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_app_instances.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.sandbox_app_instances import SandboxAppInstances # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxAppInstances(unittest.TestCase): + """SandboxAppInstances unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testSandboxAppInstances(self): + """Test SandboxAppInstances""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.sandbox_app_instances.SandboxAppInstances() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_app_instances_api.py b/examples/demo9/python/mecapp/test/test_sandbox_app_instances_api.py new file mode 100644 index 0000000000000000000000000000000000000000..0df26e24b7c9950d46509a90b43007a2f520a522 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_app_instances_api.py @@ -0,0 +1,54 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.api.sandbox_app_instances_api import SandboxAppInstancesApi # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxAppInstancesApi(unittest.TestCase): + """SandboxAppInstancesApi unit test stubs""" + + def setUp(self): + self.api = SandboxAppInstancesApi() # noqa: E501 + + def tearDown(self): + pass + + def test_sandbox_app_instances_delete(self): + """Test case for sandbox_app_instances_delete + + Delete an existing application instance # noqa: E501 + """ + pass + + def test_sandbox_app_instances_get(self): + """Test case for sandbox_app_instances_get + + Get the list of the available application instance identifiers # noqa: E501 + """ + pass + + def test_sandbox_app_instances_post(self): + """Test case for sandbox_app_instances_post + + Create a new application instance identifier # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_logs_subscriptions.py b/examples/demo9/python/mecapp/test/test_sandbox_logs_subscriptions.py new file mode 100644 index 0000000000000000000000000000000000000000..dfea77134cf8c8701dd51940f814b3b862ec8842 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_logs_subscriptions.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.sandbox_logs_subscriptions import SandboxLogsSubscriptions # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxLogsSubscriptions(unittest.TestCase): + """SandboxLogsSubscriptions unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testSandboxLogsSubscriptions(self): + """Test SandboxLogsSubscriptions""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.sandbox_logs_subscriptions.SandboxLogsSubscriptions() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_logs_subscriptions_api.py b/examples/demo9/python/mecapp/test/test_sandbox_logs_subscriptions_api.py new file mode 100644 index 0000000000000000000000000000000000000000..0b3d32c53a5008b4e06cb5cc55c6407ee50bff14 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_logs_subscriptions_api.py @@ -0,0 +1,47 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.api.sandbox_logs_subscriptions_api import SandboxLogsSubscriptionsApi # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxLogsSubscriptionsApi(unittest.TestCase): + """SandboxLogsSubscriptionsApi unit test stubs""" + + def setUp(self): + self.api = SandboxLogsSubscriptionsApi() # noqa: E501 + + def tearDown(self): + pass + + def test_sandbox_logs_subscriptions_delete(self): + """Test case for sandbox_logs_subscriptions_delete + + Subscription to receive logs from the sandbox # noqa: E501 + """ + pass + + def test_sandbox_logs_subscriptions_post(self): + """Test case for sandbox_logs_subscriptions_post + + Subscription to receive logs from the sandbox # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_mec_services.py b/examples/demo9/python/mecapp/test/test_sandbox_mec_services.py new file mode 100644 index 0000000000000000000000000000000000000000..0414e33b1c10234881c4210342bee7bae188ed0e --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_mec_services.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.sandbox_mec_services import SandboxMecServices # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxMecServices(unittest.TestCase): + """SandboxMecServices unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testSandboxMecServices(self): + """Test SandboxMecServices""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.sandbox_mec_services.SandboxMecServices() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_mec_services_api.py b/examples/demo9/python/mecapp/test/test_sandbox_mec_services_api.py new file mode 100644 index 0000000000000000000000000000000000000000..bdd52dff1e17ab682b4965280a90bb9ab0204170 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_mec_services_api.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.api.sandbox_mec_services_api import SandboxMECServicesApi # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxMECServicesApi(unittest.TestCase): + """SandboxMECServicesApi unit test stubs""" + + def setUp(self): + self.api = SandboxMECServicesApi() # noqa: E501 + + def tearDown(self): + pass + + def test_sandbox_mec_services_get(self): + """Test case for sandbox_mec_services_get + + Get the list of the available MEC services # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_network_scenario.py b/examples/demo9/python/mecapp/test/test_sandbox_network_scenario.py new file mode 100644 index 0000000000000000000000000000000000000000..5a62fb4ae000aed050f9e269fc47447e15256d1c --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_network_scenario.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.sandbox_network_scenario import SandboxNetworkScenario # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxNetworkScenario(unittest.TestCase): + """SandboxNetworkScenario unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testSandboxNetworkScenario(self): + """Test SandboxNetworkScenario""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.sandbox_network_scenario.SandboxNetworkScenario() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_network_scenarios_api.py b/examples/demo9/python/mecapp/test/test_sandbox_network_scenarios_api.py new file mode 100644 index 0000000000000000000000000000000000000000..8a5378bb2da217174bec7a22ffed244aeb781ed7 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_network_scenarios_api.py @@ -0,0 +1,61 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.api.sandbox_network_scenarios_api import SandboxNetworkScenariosApi # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxNetworkScenariosApi(unittest.TestCase): + """SandboxNetworkScenariosApi unit test stubs""" + + def setUp(self): + self.api = SandboxNetworkScenariosApi() # noqa: E501 + + def tearDown(self): + pass + + def test_sandbox_individual_network_scenarios_get(self): + """Test case for sandbox_individual_network_scenarios_get + + Get description of a Network Scenario to be used. # noqa: E501 + """ + pass + + def test_sandbox_network_scenario_delete(self): + """Test case for sandbox_network_scenario_delete + + Deactivate the Network Scenario. # noqa: E501 + """ + pass + + def test_sandbox_network_scenario_post(self): + """Test case for sandbox_network_scenario_post + + Selects the Network Scenario to be activated. # noqa: E501 + """ + pass + + def test_sandbox_network_scenarios_get(self): + """Test case for sandbox_network_scenarios_get + + Get the list of the available network scenarios # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_sandbox_ue_controller_api.py b/examples/demo9/python/mecapp/test/test_sandbox_ue_controller_api.py new file mode 100644 index 0000000000000000000000000000000000000000..56ae047e48df4093b9f27f4321209c5e178e6e92 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_sandbox_ue_controller_api.py @@ -0,0 +1,47 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.api.sandbox_ue_controller_api import SandboxUEControllerApi # noqa: E501 +from swagger_client.rest import ApiException + + +class TestSandboxUEControllerApi(unittest.TestCase): + """SandboxUEControllerApi unit test stubs""" + + def setUp(self): + self.api = SandboxUEControllerApi() # noqa: E501 + + def tearDown(self): + pass + + def test_sandbox_ue_controller_get(self): + """Test case for sandbox_ue_controller_get + + Get the list of the available UEs (e.g. \"Stationary UE\") # noqa: E501 + """ + pass + + def test_sandbox_ue_controller_patch(self): + """Test case for sandbox_ue_controller_patch + + set the new value of the UE # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_scenario.py b/examples/demo9/python/mecapp/test/test_scenario.py new file mode 100644 index 0000000000000000000000000000000000000000..ca7fb93f3c4bc15f7623c7f6d769f8177718acc4 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_scenario.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.scenario import Scenario # noqa: E501 +from swagger_client.rest import ApiException + + +class TestScenario(unittest.TestCase): + """Scenario unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testScenario(self): + """Test Scenario""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.scenario.Scenario() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_scenario_config.py b/examples/demo9/python/mecapp/test/test_scenario_config.py new file mode 100644 index 0000000000000000000000000000000000000000..47bc7378dcb0a4b31982d24472b228c5ec975fd6 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_scenario_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.scenario_config import ScenarioConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestScenarioConfig(unittest.TestCase): + """ScenarioConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testScenarioConfig(self): + """Test ScenarioConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.scenario_config.ScenarioConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_service_config.py b/examples/demo9/python/mecapp/test/test_service_config.py new file mode 100644 index 0000000000000000000000000000000000000000..e9101a2b52fc021af38cfef279be8cd71c704894 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_service_config.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.service_config import ServiceConfig # noqa: E501 +from swagger_client.rest import ApiException + + +class TestServiceConfig(unittest.TestCase): + """ServiceConfig unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testServiceConfig(self): + """Test ServiceConfig""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.service_config.ServiceConfig() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_service_port.py b/examples/demo9/python/mecapp/test/test_service_port.py new file mode 100644 index 0000000000000000000000000000000000000000..2dd9c79d74c8a6115bee165dad842e94d391186e --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_service_port.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.service_port import ServicePort # noqa: E501 +from swagger_client.rest import ApiException + + +class TestServicePort(unittest.TestCase): + """ServicePort unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testServicePort(self): + """Test ServicePort""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.service_port.ServicePort() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_ue.py b/examples/demo9/python/mecapp/test/test_ue.py new file mode 100644 index 0000000000000000000000000000000000000000..8f2c518543cce779e25855c94cc6192c0f36f668 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_ue.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.ue import UE # noqa: E501 +from swagger_client.rest import ApiException + + +class TestUE(unittest.TestCase): + """UE unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testUE(self): + """Test UE""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.ue.UE() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/test/test_zone.py b/examples/demo9/python/mecapp/test/test_zone.py new file mode 100644 index 0000000000000000000000000000000000000000..3bf14c50619e0473c2475fd8e9f206f876920e30 --- /dev/null +++ b/examples/demo9/python/mecapp/test/test_zone.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + MEC Sandbox API + + The MEC Sandbox API described using OpenAPI # noqa: E501 + + OpenAPI spec version: 0.0.7 + Contact: cti_support@etsi.org + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" + +from __future__ import absolute_import + +import unittest + +import swagger_client +from swagger_client.models.zone import Zone # noqa: E501 +from swagger_client.rest import ApiException + + +class TestZone(unittest.TestCase): + """Zone unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testZone(self): + """Test Zone""" + # FIXME: construct object with mandatory attributes with example values + # model = swagger_client.models.zone.Zone() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/examples/demo9/python/mecapp/tox.ini b/examples/demo9/python/mecapp/tox.ini new file mode 100644 index 0000000000000000000000000000000000000000..a310bec97d689718c92c84c9aebc6ab031f12933 --- /dev/null +++ b/examples/demo9/python/mecapp/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py3 + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= + nosetests \ + [] diff --git a/examples/demo9/python/notebook/MEC_And_oneM2M_Tutorial.ipynb b/examples/demo9/python/notebook/MEC_And_oneM2M_Tutorial.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..73973eafd0c1ac6bc15238e67636674adbc1adf3 --- /dev/null +++ b/examples/demo9/python/notebook/MEC_And_oneM2M_Tutorial.ipynb @@ -0,0 +1,1472 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to develop an ETSI MEC application to exploit both ETSI MEC Sandbox and oneM2M platforms, based on ETSI GS MEC 046\n", + "\n", + "This tutorial introduces the step by step procedure to create a basic MEC application following ETSI MEC standards and oneM2M standards to manage devices/sensors/actuators... (MEC 033, MEC 046).\n", + "It uses two platforms:\n", + "1. The ETSI MEC Sandbox and,\n", + "2. a oneM2M platform.\n", + "\n", + "In this tutorial, we will use the [ACME oneM2M platform](https://acmecse.net/) with some changes to register to the ETSI MEC platform. \n", + "\n", + "
    \n", + " Note: These source code examples are simplified and ignore return codes and error checks to a large extent. We do this to highlight how to use the MEC Sandbox API and the different MEC satndards and reduce unrelated code.\n", + "A real-world application will of course properly check every return value and exit correctly at the first serious error.\n", + "
    " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is the list of the questions and topics to clarify from the ETSI MEC perspective to support an efficient support of oneM2M features \n", + "
    \n", + " Note: FIXMEs\n", + " - How to register MEC/oneM2M platforms together (currently the oneM2M platform is registering to the ETSI MEC sandbox platform) \n", + " - How to setup the testbed for the tutorial\n", + " - How to link oneM2M resources (AE, container, flex-container...) and SAREF onthologies with existing MEC standards\n", + " - \n", + "
    \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What is a ETSI MEC application\n", + "\n", + "For more details on how to develop an ETSI MEC application, please refer to [The Wiki MEC web site](https://www.etsi.org/technologies/multi-access-edge-computing) and the [ETSI MEC application tutorial](http://mec-platform2.etsi.org:9999/notebooks/work/notebook/MEC%20application.ipynb).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The basics to develop an ETSI MEC application\n", + "\n", + "All the code below are helpers functions required to start the development our ETSI MEC application.\n", + "For more details, please refer to the[ETSI MEC application tutorial](http://mec-platform2.etsi.org:9999/notebooks/work/notebook/MEC%20application.ipynb).\n", + "\n", + "
    \n", + " Note: The sub-paragraph 'Putting everything together' is a specific paragraph where all the newly features introduced in the main paragraph are put together to create an executable block of code. It is possible to skip this block of code by removing the comment character (#) on first line of this block of code.\n", + "
    \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.chdir(os.path.join(os.getcwd(), '../mecapp'))\n", + "print(os.getcwd())\n", + "from __future__ import division # Import floating-point division (1/4=0.25) instead of Euclidian division (1/4=0)\n", + "\n", + "import os\n", + "import sys\n", + "import re\n", + "import logging\n", + "import threading\n", + "import time\n", + "import json\n", + "import uuid\n", + "\n", + "import pprint\n", + "\n", + "import six\n", + "\n", + "import swagger_client\n", + "from swagger_client.rest import ApiException\n", + "\n", + "from http import HTTPStatus\n", + "from http.server import BaseHTTPRequestHandler, HTTPServer\n", + "\n", + "try:\n", + " import urllib3\n", + "except ImportError:\n", + " raise ImportError('Swagger python client requires urllib3.')\n", + "\n", + "MEC_SANDBOX_URL = 'https://mec-platform.etsi.org' # MEC Sandbox host/base URL\n", + "MEC_SANDBOX_API_URL = 'https://mec-platform.etsi.org/sandbox-api/v1' # MEC Sandbox API host/base URL\n", + "PROVIDER = 'Jupyter2024' # Login provider value - To skip authorization: 'github'\n", + "MEC_PLTF = 'mep1' # MEC plateform name. Linked to the network scenario\n", + "LOGGER_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # Logging format\n", + "STABLE_TIME_OUT = 10 # Timer to wait for MEC Sndbox reaches its stable state (K8S pods in running state)\n", + "LOGIN_TIMEOUT = 3 #30 # Timer to wait for user to authorize from GITHUB\n", + "LISTENER_IP = '0.0.0.0' # Listener IPv4 address for notification callback calls\n", + "LISTENER_PORT = 31111 # Listener IPv4 port for notification callback calls. Default: 36001\n", + "CALLBACK_URI = 'http://mec-platform.etsi.org:31111/sandbox/v1'\n", + "OM2M_REGISTRATION = 30 # Timer to wait for user to register sensors and devices to oneM2M platform\n", + "\n", + "# Initialize the logger\n", + "logger = logging.getLogger(__name__)\n", + "logger.setLevel(logging.DEBUG)\n", + "logging.basicConfig(filename='/tmp/' + time.strftime('%Y%m%d-%H%M%S') + '.log')\n", + "l = logging.StreamHandler()\n", + "l.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))\n", + "logger.addHandler(l)\n", + "\n", + "# Setup the HTTP REST API configuration to be used to send request to MEC Sandbox API \n", + "configuration = swagger_client.Configuration()\n", + "configuration.host = MEC_SANDBOX_API_URL\n", + "configuration.verify_ssl = True\n", + "configuration.debug = True\n", + "configuration.logger_format = LOGGER_FORMAT\n", + "\n", + "# Create an instance of ApiClient\n", + "sandbox_api = swagger_client.ApiClient(configuration, 'Content-Type', 'application/json')\n", + "\n", + "# Setup the HTTP REST API configuration to be used to send request to MEC Services\n", + "configuration1 = swagger_client.Configuration()\n", + "configuration1.host = MEC_SANDBOX_URL\n", + "configuration1.verify_ssl = True\n", + "configuration1.debug = True\n", + "configuration1.logger_format = LOGGER_FORMAT\n", + "\n", + "# Create an instance of ApiClient\n", + "service_api = swagger_client.ApiClient(configuration1, 'Content-Type', 'application/json')\n", + "\n", + "# Initialize the global variables\n", + "nw_scenarios = [] # The list of available network scenarios\n", + "nw_scenario_idx = -1 # The network scenario idx to activate (deactivate)\n", + "app_inst_id = None # The requested application instance identifier\n", + "got_notification = False # Set to true if a POST notification is received\n", + "\n", + "def process_login() -> str:\n", + " \"\"\"\n", + " Authenticate and create a new MEC Sandbox instance.\n", + " :return: The sandbox instance identifier on success, None otherwise\n", + " \"\"\" \n", + " global PROVIDER, logger\n", + "\n", + " logger.debug('>>> process_login')\n", + "\n", + " try:\n", + " auth = swagger_client.AuthorizationApi(sandbox_api)\n", + " oauth = auth.login(PROVIDER, async_req = False)\n", + " logger.debug('process_login (step1): oauth: ' + str(oauth))\n", + " # Wait for the MEC Sandbox is running\n", + " logger.debug('=======================> DO AUTHORIZATION WITH CODE : ' + oauth.user_code)\n", + " logger.debug('=======================> DO AUTHORIZATION HERE : ' + oauth.verification_uri)\n", + " if oauth.verification_uri == \"\":\n", + " time.sleep(LOGIN_TIMEOUT) # Skip scecurity, wait for a few seconds\n", + " else:\n", + " time.sleep(10 * LOGIN_TIMEOUT) # Wait for Authirization from user side\n", + " namespace = auth.get_namespace(oauth.user_code)\n", + " logger.debug('process_login (step2): result: ' + str(namespace))\n", + " return namespace.sandbox_name\n", + " except ApiException as e:\n", + " logger.error('Exception when calling AuthorizationApi->login: %s\\n' % e)\n", + "\n", + " return None\n", + " # End of function process_login\n", + "\n", + "def process_logout(sandbox_name: str) -> int:\n", + " \"\"\"\n", + " Delete the specified MEC Sandbox instance.\n", + " :param sandbox_name: The MEC Sandbox to delete\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> process_logout: sandbox=' + sandbox_name)\n", + "\n", + " try:\n", + " auth = swagger_client.AuthorizationApi(sandbox_api)\n", + " result = auth.logout(sandbox_name, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling AuthorizationApi->logout: %s\\n' % e)\n", + " return -1\n", + " # End of function process_logout\n", + "\n", + "\n", + "def get_network_scenarios(sandbox_name: str) -> list:\n", + " \"\"\"\n", + " Retrieve the list of the available network scenarios.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: The list of the available network scenarios on success, None otherwise\n", + " \"\"\"\n", + " global PROVIDER, logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> get_network_scenarios: sandbox=' + sandbox_name)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxNetworkScenariosApi(sandbox_api)\n", + " result = nw.sandbox_network_scenarios_get(sandbox_name, async_req = False) # noqa: E501\n", + " logger.debug('get_network_scenarios: result: ' + str(result))\n", + " return result\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxNetworkScenariosApi->sandbox_network_scenarios_get: %s\\n' % e)\n", + "\n", + " return None\n", + " # End of function get_network_scenarios\n", + "\n", + "def select_network_scenario_based_on_criteria(criterias_list: list) -> int:\n", + " \"\"\"\n", + " Select the network scenario to activate based of the provided list of criterias.\n", + " :param criterias_list: The list of criterias to select the correct network scenario\n", + " :return: 0 on success, -1 otherwise\n", + " :remark: The current mask is:\n", + " [{'id': '4g-5g-macro-v2x'}, {'id': '4g-5g-macro-v2x-fed'}, {'id': '4g-5g-mc-v2x-fed-iot'}, {'id': '4g-5g-wifi-macro'}, {'id': '4g-macro'}, {'id': '4g-wifi-macro'}, {'id': 'dual-mep-4g-5g-wifi-macro'}, {'id': 'dual-mep-short-path'}]\n", + " | | | | | | | |\n", + " ||-------------------------------- | | | | | |\n", + " ||||------------------------------------------------------------- | | | | |\n", + " |||||------------------------------------------------------------------------------------------ | | | |\n", + " ||||||------------------------------------------------------------------------------------------------------------------ | | |\n", + " |||||||------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n", + " ||||||| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n", + " 0000000011111111b\n", + " \"\"\"\n", + " global PROVIDER, logger\n", + "\n", + " logger.debug('>>> select_network_scenario_based_on_criteria: criterias_list=' + str(criterias_list))\n", + "\n", + " if len(criterias_list) == 0:\n", + " return 0 # The index of the '4g-5g-macro-v2x' network scenario - Hard coded\n", + "\n", + " selector = 0xFF\n", + " for item in criterias_list:\n", + " if item == 'v2x':\n", + " selector ^= 0x1F\n", + " elif item == 'fed':\n", + " selector ^= 0x9F\n", + " elif item == 'iot':\n", + " selector ^= 0xDF\n", + " elif item == 'wifi':\n", + " selector ^= 0xEF\n", + " elif item == 'dual':\n", + " selector ^= 0xFE\n", + " # End of 'for' statement\n", + " logger.debug('select_network_scenario_based_on_criteria: selector=' + str(selector))\n", + " selected = 0 # The index of the '4g-5g-macro-v2x' network scenario - Hard coded\n", + " if (selector & 0x40) == 0x40:\n", + " selected = 1\n", + " elif (selector & 0x20) == 0x20:\n", + " selected = 2\n", + " elif (selector & 0x10) == 0x10:\n", + " selected = 3\n", + " elif (selector & 0x08) == 0x08:\n", + " selected = 4\n", + " elif (selector & 0x04) == 0x04:\n", + " selected = 5\n", + " elif (selector & 0x02) == 0x02:\n", + " selected = 6\n", + " elif (selector & 0x01) == 0x01:\n", + " selected = 7\n", + " logger.debug('select_network_scenario_based_on_criteria: selected=' + str(selected))\n", + "\n", + " return selected\n", + " # End of function select_network_scenario_based_on_criteria\n", + "\n", + "def activate_network_scenario(sandbox_name: str) -> int:\n", + " \"\"\"\n", + " Activate the specified network scenario.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('>>> activate_network_scenario: ' + sandbox_name)\n", + "\n", + " nw_scenario_idx = select_network_scenario_based_on_criteria(['iot'])\n", + " if nw_scenario_idx == -1:\n", + " logger.error('activate_network_scenario: Failed to select a network scenarion')\n", + " return -1\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxNetworkScenariosApi(sandbox_api)\n", + " nw.sandbox_network_scenario_post(sandbox_name, nw_scenarios[nw_scenario_idx].id, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxNetworkScenariosApi->activate_network_scenario: %s\\n' % e)\n", + "\n", + " return -1\n", + " # End of function activate_network_scenario\n", + "\n", + "def deactivate_network_scenario(sandbox: str) -> int:\n", + " \"\"\"\n", + " Deactivate the current network scenario.\n", + " :param sandbox: The MEC Sandbox instance to use\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, nw_scenarios, nw_scenario_idx\n", + "\n", + " logger.debug('>>> deactivate_network_scenario: ' + sandbox)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxNetworkScenariosApi(sandbox_api)\n", + " nw.sandbox_network_scenario_delete(sandbox, nw_scenarios[nw_scenario_idx].id, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxNetworkScenariosApi->deactivate_network_scenario: %s\\n' % e)\n", + "\n", + " return -1\n", + " # End of function deactivate_network_scenario\n", + "\n", + "def request_application_instance_id(sandbox_name: str) -> swagger_client.models.ApplicationInfo:\n", + " \"\"\"\n", + " Request the creation of a new MEC application instance identifier.\n", + " It is like the MEC application was instanciated by the MEC platform and it is executed locally.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: The MEC application instance identifier on success, None otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.2 MEC application start-up\n", + " \"\"\"\n", + " global MEC_PLTF, logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> request_application_instance_id: ' + sandbox_name)\n", + "\n", + " # Create a instance of our MEC application\n", + " try:\n", + " a = swagger_client.models.ApplicationInfo(id=str(uuid.uuid4()), name='JupyterMecApp', node_name=MEC_PLTF, type='USER') # noqa: E501\n", + " nw = swagger_client.SandboxAppInstancesApi(sandbox_api)\n", + " result = nw.sandbox_app_instances_post(a, sandbox_name, async_req = False) # noqa: E501\n", + " logger.debug('request_application_instance_id: result: ' + str(result))\n", + " return result\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxAppInstancesApi->sandbox_app_instances_post: %s\\n' % e)\n", + "\n", + " return None\n", + " # End of function request_application_instance_id\n", + "\n", + "def delete_application_instance_id(sandbox_name: str, app_inst_id: str) -> int:\n", + " \"\"\"\n", + " Request the deletion of a MEC application.\n", + " :param sandbox: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> delete_application_instance_id: ' + sandbox_name)\n", + " logger.debug('>>> delete_application_instance_id: ' + app_inst_id)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxAppInstancesApi(sandbox_api)\n", + " nw.sandbox_app_instances_delete(sandbox_name, app_inst_id, async_req = False) # noqa: E501\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxAppInstancesApi->sandbox_app_instances_delete: %s\\n' % e)\n", + "\n", + " return -1\n", + " # End of function deletet_application_instance_id\n", + "\n", + "def get_applications_list(sandbox_name: str) -> list:\n", + " \"\"\"\n", + " Request the list of the MEC application available on the MEC Platform.\n", + " :param sandbox: The MEC Sandbox instance to use\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, sandbox_api, configuration\n", + "\n", + " logger.debug('>>> get_applications_list: ' + sandbox_name)\n", + "\n", + " try:\n", + " nw = swagger_client.SandboxAppInstancesApi(sandbox_api)\n", + " result = nw.sandbox_app_instances_get(sandbox_name, async_req = False) # noqa: E501\n", + " logger.debug('get_applications_list: result: ' + str(result))\n", + " return result\n", + " except ApiException as e:\n", + " logger.error('Exception when calling SandboxAppInstancesApi->get_applications_list: %s\\n' % e)\n", + " return None \n", + " # End of function delete_application_instance_id\n", + "\n", + "def send_ready_confirmation(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> int:\n", + " \"\"\"\n", + " Send the ready_confirmation to indicate that the MEC application is active.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: 0 on success, -1 otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.2 MEC application start-up - Step 4c\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_ready_confirmation: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/confirm_ready'\n", + " logger.debug('send_ready_confirmation: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # JSON indication READY\n", + " dict_body = {}\n", + " dict_body['indication'] = 'READY'\n", + " service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=dict_body, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function send_ready_confirmation\n", + "\n", + "def send_subscribe_termination(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> object:\n", + " \"\"\"\n", + " Subscribe to the MEC application termination notifications.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: The HTTP respone, the subscription ID and the resource URL on success, None otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.6b Receiving event notifications on MEC application instance termination\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_subscribe_termination: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/subscriptions'\n", + " logger.debug('send_subscribe_termination: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # Body\n", + " dict_body = {}\n", + " dict_body['subscriptionType'] = 'AppTerminationNotificationSubscription'\n", + " dict_body['callbackReference'] = CALLBACK_URI + '/mec011/v2/termination' # FIXME To be parameterized\n", + " dict_body['appInstanceId'] = app_inst_id.id\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=dict_body, async_req=False)\n", + " return (result, extract_sub_id(headers['Location']), headers['Location'])\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, status, None)\n", + " # End of function send_subscribe_termination\n", + "\n", + "def extract_sub_id(resource_url: str) -> str:\n", + " \"\"\"\n", + " Extract the subscription identifier from the specified subscription URL.\n", + " :param resource_url: The subscription URL\n", + " :return: The subscription identifier on success, None otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> extract_sub_id: resource_url: ' + resource_url)\n", + "\n", + " res = urllib3.util.parse_url(resource_url)\n", + " if res is not None and res.path is not None and res.path != '':\n", + " id = res.path.rsplit('/', 1)[-1]\n", + " if id is not None:\n", + " return id\n", + " return None\n", + " # End of function extract_sub_id\n", + "\n", + "def delete_subscribe_termination(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo, sub_id: str) -> int:\n", + " \"\"\"\n", + " Delete the subscrition to the AppTermination notification.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> delete_subscribe_termination: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/subscriptions/{sub_id}'\n", + " logger.debug('delete_subscribe_termination: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " path_params['sub_id'] = sub_id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " service_api.call_api(url, 'DELETE', header_params=header_params, path_params = path_params, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function delete_subscribe_termination\n", + "\n", + "def send_termination_confirmation(sandbox_name: str, app_inst_id: swagger_client.models.application_info.ApplicationInfo) -> int:\n", + " \"\"\"\n", + " Send the confirm_termination to indicate that the MEC application is terminating gracefully.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :return: 0 on success, -1 otherwise\n", + " :see ETSI GS MEC 011 V3.2.1 (2024-04) Clause 5.2.3 MEC application graceful termination/stop\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> send_termination_confirmation: ' + app_inst_id.id)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/mec_app_support/v2/applications/{app_inst_id}/confirm_termination'\n", + " logger.debug('send_termination_confirmation: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " path_params['app_inst_id'] = app_inst_id.id\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # JSON indication READY\n", + " dict_body = {}\n", + " dict_body['operationAction'] = 'TERMINATING'\n", + " service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=dict_body, async_req=False)\n", + " return 0\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return -1\n", + " # End of function send_termination_confirmation\n", + "\n", + "def mec_app_setup():\n", + " \"\"\"\n", + " This function provides the steps to setup a MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Request for a new application instance identifier\n", + " - Send READY confirmation\n", + " - Subscribe to AppTermination Notification\n", + " :return The MEC Sandbox instance, the MEC application instance identifier and the subscription identifier on success, None otherwise\n", + " \"\"\"\n", + " global logger, nw_scenarios\n", + "\n", + " # Login\n", + " sandbox = process_login()\n", + " if sandbox is None:\n", + " logger.error('Failed to instanciate a MEC Sandbox')\n", + " return None\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Print available network scenarios\n", + " nw_scenarios = get_network_scenarios(sandbox)\n", + " if nw_scenarios is None:\n", + " logger.error('Failed to retrieve the list of network scenarios')\n", + " elif len(nw_scenarios) != 0:\n", + " # Wait for the MEC Sandbox is running\n", + " time.sleep(STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + " else:\n", + " logger.info('nw_scenarios: No scenario available')\n", + "\n", + " # Activate a network scenario based on a list of criterias (hard coded!!!)\n", + " if activate_network_scenario(sandbox) == -1:\n", + " logger.error('Failed to activate network scenario')\n", + " else:\n", + " # Wait for the MEC services are running\n", + " time.sleep(2 * STABLE_TIME_OUT) # Wait for k8s pods up and running\n", + "\n", + " # Request for a new application instance identifier\n", + " app_inst_id = request_application_instance_id(sandbox)\n", + " if app_inst_id == None:\n", + " logger.error('Failed to request an application instance identifier')\n", + " else:\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Send READY confirmation\n", + " sub_id = None\n", + " if send_ready_confirmation(sandbox, app_inst_id) == -1:\n", + " logger.error('Failed to send confirm_ready')\n", + " else:\n", + " # Subscribe to AppTerminationNotificationSubscription\n", + " result, sub_id, res_url = send_subscribe_termination(sandbox, app_inst_id)\n", + " if sub_id == None:\n", + " logger.error('Failed to do the subscription')\n", + "\n", + " return (sandbox, app_inst_id, sub_id)\n", + " # End of function mec_app_setup\n", + "\n", + "def mec_app_termination(sandbox_name: str, app_inst_id:swagger_client.models.ApplicationInfo, sub_id: str):\n", + " \"\"\"\n", + " This function provides the steps to setup a MEC application:\n", + " - Login\n", + " - Print sandbox identifier\n", + " - Print available network scenarios\n", + " - Activate a network scenario\n", + " - Request for a new application instance identifier\n", + " - Send READY confirmation\n", + " - Subscribe to AppTermination Notification\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param app_inst_id: The MEC application instance identifier\n", + " :param sub_id: The subscription identifier\n", + " \"\"\"\n", + " global logger\n", + "\n", + " # Delete AppTerminationNotification subscription\n", + " if sub_id is not None:\n", + " if delete_subscribe_termination(sandbox_name, app_inst_id, sub_id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + "\n", + " # Delete the application instance identifier\n", + " if delete_application_instance_id(sandbox_name, app_inst_id.id) == -1:\n", + " logger.error('Failed to delete the application instance identifier')\n", + " else:\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(STABLE_TIME_OUT)\n", + "\n", + " # Deactivate a network scenario based on a list of criterias (hard coded!!!)\n", + " if deactivate_network_scenario(sandbox_name) == -1:\n", + " logger.error('Failed to deactivate network scenario')\n", + " else:\n", + " # Wait for the MEC services are terminated\n", + " time.sleep(2 * STABLE_TIME_OUT)\n", + "\n", + " # Logout\n", + " process_logout(sandbox_name)\n", + " # End of function mec_app_termination\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create our first ETSI MEC application: the skeleton of our MEC application\n", + "\n", + "The purpose of this section is to create a first skeloton of our final MEC application by retrieving the oneM2M platform identifier following the procedure described in MEC 033.\n", + "\n", + "## Accessing the ETSI MEC Sandbox\n", + "\n", + "The cell code above include a section where the ETSI MEC Sandbox setting are already filled to make this tutorial running well (see constants MEC_SANDBOX_URL and MEC_SANDBOX_API_URL).\n", + "\n", + "## Hosting the oneM2M platform\n", + "\n", + "All the details about the oneM2M platform are provided during the registration to the ETSI MEC Sanbox registration process.\n", + "The tricky point is that the oneM2M platform shall address the same ETSI MEC Sandbox configured in the cell code above (see constants MEC_SANDBOX_URL and MEC_SANDBOX_API_URL).\n", + "\n", + "## Wait for the oneM2M platform to register to the ETSI MEC platform\n", + "\n", + "Here, we just going to poll every 5 seconds to verify if the oneM2M platform is registered to the ETSI MEC platform.\n", + "After a minutes, this function returns with than error.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def check_is_om2m_platform_registered(sandbox_name: str, timeout: int) -> str:\n", + " \"\"\"\n", + " To retrieves the first regitered oneM2M platform\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return The oneM2M platform identifier on success, an empty string otherwise\n", + " :see ETSI GS MEC 033 V3.1.1 (2022-12) Clause 5.3.2 Registered IoT platforms query\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> check_is_om2m_platform_registered')\n", + " expiry = 0\n", + " while expiry < timeout: # x seconds\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/iots/v1/registered_iot_platforms'\n", + " logger.debug('check_is_om2m_platform_registered: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, async_req=False)\n", + " if status == 200:\n", + " # Extract the oneM2M platform identifier\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: ' + str(data))\n", + " if len(data) > 0:\n", + " return data[0]['iotPlatformId']\n", + " else:\n", + " logger.error('check_is_om2m_platform_registered: Empty response\\n')\n", + " else:\n", + " logger.error('Failed to do discover, #tries=' + str(tries))\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + "\n", + " expiry += 10\n", + " time.sleep(10) # Wait for 10 seconds \n", + " # End of 'while' statement\n", + " return \"\"\n", + " # End of function check_is_om2m_platform_registered" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Putting everything together\n", + "\n", + "It is time now to create the our third iteration of our ETSI MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Create an ETSI MEC application instance\n", + "- Wait for a oneM2M platform to register to the ETSI MEC platform\n", + "- Display the oneM2M platform identifier\n", + "- Terminate the ETSI MEC application instance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Create an ETSI MEC application instance\n", + " - Wait for a oneM2M platform to register to the ETSI MEC platform (1 minute max.)\n", + " - Display the oneM2M platform identifier\n", + " - Terminate the ETSI MEC application instance\n", + " \"\"\" \n", + " global logger, nw_scenarios\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC platform and the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Wait for a oneM2M platform to register to the ETSI MEC platform\n", + " om2m_pltf_id = check_is_om2m_platform_registered(sandbox_name, 240)\n", + " if om2m_pltf_id != \"\":\n", + " logger.info('om2m_pltf_id: %s', str(om2m_pltf_id))\n", + " else:\n", + " logger.info('Timeout')\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Second round: Discover the list of sensors and devices available\n", + "\n", + "The purpose of this second application is to discover all the sencors and devices availble on the oneM2M platform.\n", + "\n", + "## What is sensors and devices discovery\n", + "\n", + "The role of the discovery is to retrieve the list of all sensors and devices, based on some filter criteria.\n", + "These filter criteria are (ETSI GS MEC 046 V3.1.1 (2024-04) Clause 5.3.2 Sensor Discovery Lookup):\n", + "- Type of sensor;\n", + "- A list of properties that the sensor can sense;\n", + "- A list of characteristics that sensors and device shall match;\n", + "- An geographical area that sensors and device shall match.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def om2m_discover_devices(sandbox_name: str, type_: str) -> dict:\n", + " \"\"\"\n", + " This function performs a discovery of all available sensors and devices based on type\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param type_: The MEC Sandbox instance to use\n", + " :return The discovered sensors & devices on success, an None otherwise\n", + " :see ETSI GS MEC 046 V3.1.1 (2024-04) Clause 5.3.2 Sensor Discovery Lookup\n", + " :see ETSI GS MEC 046 V3.1.1 (2024-04) Clause 7.3.3.1 GET\n", + " \"\"\" \n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/sens/v1/queries/sensor_discovery'\n", + " logger.debug('om2m_discover_devices: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " # Query parameters\n", + " query_params = []\n", + " query_params.append(('type', type_))\n", + " query_params.append(('sensorPropertyList', 'saref:any'))\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, query_params=query_params, async_req=False)\n", + " if status == 200:\n", + " # Extract the oneM2M platform identifier\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: ' + str(data))\n", + " return data\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return None\n", + " # End of function om2m_discover_devices\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preconditions\n", + "\n", + "To be efficient, some sensors and devices shall be registered on the oneM2M platform.\n", + "Here is a little project simulating a basic smart garden based on ESP32. To use it, you do not need any material. This project uses the well known Wokwi playground to simulate the ESP32. It provides you a free license to play with it and this is enough for our needs.\n", + "\n", + "You are just required to use Visual Code Studio or Cursor IDE with PlatformIO extention installed. Next, follow the instructions from the README file to build, configure and execute this project.\n", + "\n", + "The references below will point you to the project code\n", + "- The [ESP32 project](https://gitlab.com/garcia.yann/lolin32esp32project-01.git)\n", + "- The [wokwi web site](https://wokwi.com/)\n", + "\n", + "Assuming this ESP32 project is deployed, the preconditions() function below is just (for 30 seconds) waiting for you to execute it for 30 seconds.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def preconditions():\n", + " \"\"\"\n", + " This function performs a discovery of all available sensors and devices based on type\n", + " \"\"\" \n", + " global OM2M_REGISTRATION\n", + "\n", + " logger.debug('=======================> Please run script to register sensors & devices')\n", + " time.sleep(OM2M_REGISTRATION) # Wait\n", + " # End of function preconditions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Putting everything together\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the followin\n", + "- Create an ETSI MEC application instance\n", + "- Wait for a oneM2M platform to register to the ETSI MEC platform (1 minute max.)\n", + "- Discover the list of sensors and devices of type \n", + "- Display the result of the discovery\n", + "- Terminate the ETSI MEC application instance\n", + "g:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Create an ETSI MEC application instance\n", + " - Wait for a oneM2M platform to register to the ETSI MEC platform (1 minute max.)\n", + " - Discover the list of sensors and devices of type \n", + " - Display the result of the discovery\n", + " - Terminate the ETSI MEC application instance\n", + " \"\"\" \n", + " global logger, nw_scenarios\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Wait for a oneM2M platform to register to the ETSI MEC platform\n", + " om2m_pltf_id = check_is_om2m_platform_registered(sandbox_name, 240)\n", + " if om2m_pltf_id != \"\":\n", + " logger.info('om2m_pltf_id: %s', str(om2m_pltf_id))\n", + " else:\n", + " logger.warning('Timeout')\n", + "\n", + " # Ask user to register sensors & devices to the oneM2M platform\n", + " preconditions()\n", + "\n", + " # Do the discovery\n", + " tries = 1\n", + " sensors = om2m_discover_devices(sandbox_name, 'CNT')\n", + " while tries < 5 and sensors is None:\n", + " tries += 1\n", + " logger.error('Failed to do discover, #tries=' + str(tries))\n", + " time.sleep(10) # wait for x seconds\n", + " sensors = om2m_discover_devices(sandbox_name, 'CNT')\n", + " # End of 'while' statement\n", + " if not sensors is None:\n", + " logger.info('oneM2M sensors and devices discovery: ' + str(sensors))\n", + " else:\n", + " logger.error('Failed to do discover')\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Third round: Retrieve information of a specific sensor/device\n", + "\n", + "We are now able to develop an ETSI MEC application which is able to discover a list of sensors/devices based on different criteria.\n", + "The next step is to retrieve information of a specific sensor we discovered.\n", + "\n", + "## Selecting a sensors/devices\n", + "\n", + "Having a list of sensors/devices, the next step is to select one of them to retrieve information about it.\n", + "\n", + "In this section, we wl iluse a function to do this selection based on criterao\n", + "\n", + "**Note:** For the time being, this function returns the first sensor/device in the list. It would be a good exercise to refine this function ;). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def select_sensors_based_on_criteria(sensors: list, criterias: dict) -> str:\n", + " \"\"\"\n", + " Select a sensor/device based of the provided list of criterias.\n", + " :param criterias_list: The list of criterias to select the correct sensor\n", + " - sensorType\n", + " :return: The sensor/device identifier on success, an empty string otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " # Set default values\n", + " type_ = 'FLX'\n", + " # TODO To be continued\n", + "\n", + " # Update default values\n", + " if not criterias is None and len(criterias) > 0:\n", + " if 'sensorType' in criterias.keys():\n", + " type_ = criterias['sensorType']\n", + " # TODO To be continued\n", + "\n", + " # Let's find the sensor matching criteria\n", + " for item in sensors:\n", + " #logger.debug('select_sensors_based_on_criteria: Processing item: ' + str(item))\n", + " if item['sensorType'] != type_:\n", + " continue\n", + " return item['sensorIdentifier']\n", + " # End of 'for' statement\n", + " return ''\n", + " # End of function select_sensors_based_on_criteria" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieving information of a specific sensor\n", + "\n", + "The purpose here is the to retrieve sensor data desscription based on ETSI GS MEC 046 V3.1.1 (2024-04) Clause 5.3.6 Sensor Data Lookup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def om2m_get_sensor_data(sandbox_name: str, sensor_id: str) -> dict:\n", + " \"\"\"\n", + " This function retrieves the information of a specific sensor/device\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :param sensor_id: The sensor identifier\n", + " :return The discovered sensors & devices on success, an None otherwise\n", + " :see ETSI GS MEC 046 V3.1.1 (2024-04) Clause 5.3.6 Sensor Data Lookup\n", + " :see ETSI GS MEC 046 V3.1.1 (2024-04) Clause 7.9.3.1 GET\n", + " \"\"\" \n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}/sens/v1/queries/sensor_data'\n", + " logger.debug('om2m_discover_devices: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " # Query parameters\n", + " query_params = []\n", + " query_params.append(('sensorIdentifier', sensor_id))\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " (result, status, headers) = service_api.call_api(url, 'GET', header_params=header_params, path_params=path_params, query_params=query_params, async_req=False)\n", + " if status == 200:\n", + " # Extract the oneM2M platform identifier\n", + " logger.info('body: ' + str(result.data))\n", + " data = json.loads(result.data)\n", + " logger.info('data: ' + str(data))\n", + " return data\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return None\n", + " # End of function om2m_get_sensor_data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Putting everything together\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Create an ETSI MEC application instance\n", + "- Wait for a oneM2M platform to register to the ETSI MEC platform (1 minute max.)\n", + "- Discover the list of sensors and devices of type\n", + "- Select a specific sensor/device\n", + "- Retrive its SensorData descritpion (ETSI GS MEC 046 V3.1.1 (2024-04) Clause 6.2.3 Type: SensorData)\n", + "- Display the result of the discovery\n", + "- Terminate the ETSI MEC application instance\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Create an ETSI MEC application instance\n", + " - Wait for a oneM2M platform to register to the ETSI MEC platform (1 minute max.)\n", + " - Discover the list of sensors and devices of type \n", + " - Display the result of the discovery\n", + " - Terminate the ETSI MEC application instance\n", + " \"\"\" \n", + " global logger, nw_scenarios\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Wait for a oneM2M platform to register to the ETSI MEC platform\n", + " om2m_pltf_id = check_is_om2m_platform_registered(sandbox_name, 240)\n", + " if om2m_pltf_id != \"\":\n", + " logger.info('om2m_pltf_id: %s', str(om2m_pltf_id))\n", + " else:\n", + " logger.warning('Timeout')\n", + "\n", + " # Ask user to register sensors & devices to the oneM2M platform\n", + " preconditions()\n", + "\n", + " # Do the discovery\n", + " tries = 1\n", + " sensors = om2m_discover_devices(sandbox_name, 'CNT')\n", + " while tries < 3 and sensors is None:\n", + " tries += 1\n", + " logger.error('Failed to do discover, #tries=' + str(tries))\n", + " time.sleep(15) # wait for 10s\n", + " sensors = om2m_discover_devices(sandbox_name, 'CNT')\n", + " # End of 'while' statement\n", + " if not sensors is None:\n", + " logger.info('oneM2M sensors and devices discovery: ' + str(sensors))\n", + " sensor_id = select_sensors_based_on_criteria(sensors, None)\n", + " if sensor_id != '':\n", + " info = om2m_get_sensor_data(sandbox_name, sensor_id)\n", + " if not info is None:\n", + " desc = next((item for item in sensors if item['sensorIdentifier'] == sensor_id), None)\n", + " logger.info('oneM2M sensor descr: ' + str(desc))\n", + " logger.info('oneM2M sensor info: ' + str(info))\n", + " else:\n", + " logger.error('Failed to do retrieve info for ' + sensor_id)\n", + " else:\n", + " logger.error('Failed to find a sensor/device matching criteria')\n", + " else:\n", + " logger.error('Failed to do discover')\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fourth round: How to use subscriptions\n", + "\n", + "The purpose here is to use subscription on sensors/devices to be notified when a event occurs, such as an update of the sensor/device, a new measurement...\n", + "According to ETSI GS MEC 046 V3.1.1 (2024-04) Clause 5.3 Sequence diagrams, there are several kind of subscription:\n", + "- Sensor discovery subscription (clause 5.3.3 Sensor Discovery Subscription);\n", + "- Sensor Status subscription (clause 5.3.5 Sensor Status Subscription);\n", + "- Sensor Data subscription (clause 5.3.7 Sensor Data Subscription).\n", + "\n", + "In any case, the mechanism is the same: \n", + "1. Our MEC application shall create a subscription request to get a subscription identifier\n", + "2. Our MEC application receives notification (see next section below)\n", + "3. Our Our MEC application cancel the subscription when terminating\n", + "\n", + "The cell below provides the code to create our subscription." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Notification support\n", + "\n", + "To recieve notification, our MEC application is required to support an HTTP listenener to recieve POST requests from the MEC Sandbox and reply to them: this is the notification mechanism.\n", + "\n", + "The class HTTPRequestHandler (see cell below) provides the suport of such mechanism.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class HTTPServer_RequestHandler(BaseHTTPRequestHandler):\n", + " \"\"\"\n", + " Minimal implementation of an HTTP server (http only).\n", + " \"\"\"\n", + "\n", + " def do_GET(self):\n", + " logger.info('>>> do_GET: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_GET: ' + ctype)\n", + "\n", + " message = ''\n", + " if self.path == '/sandbox/v1/statistic/v1/quantity':\n", + " logger.info('do_GET: Computing statistic quantities for application MEC service')\n", + " # TODO Add logit to our MEC service\n", + " message = '{\"time\":20180124,\"avg\": 0.0,\"max\": 0.0,\"min\": 0.0,\"stddev\": 0.0 }'\n", + " else:\n", + " # Send error message\n", + " message = '{\"title\":\"Unknown URI\",\"type\":\"do_GET.parser\",\"status\":404 }'\n", + " logger.info('do_GET: message: ' + message)\n", + " \n", + " # Send response status code\n", + " self.send_response(HTTPStatus.OK)\n", + "\n", + " # Send headers\n", + " self.send_header('Content-type','text/plain; charset=utf-8')\n", + " self.send_header('Content-length', str(len(message)))\n", + " self.end_headers()\n", + "\n", + " # Write content as utf-8 data\n", + " self.wfile.write(message.encode('utf8'))\n", + " return\n", + " # End of function do_GET\n", + "\n", + " def do_POST(self):\n", + " global got_notification\n", + "\n", + " logger.info('>>> do_POST: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_POST: ' + ctype)\n", + "\n", + " content_len = int(self.headers.get('Content-Length'))\n", + " if content_len != 0:\n", + " body = self.rfile.read(content_len).decode('utf8')\n", + " logger.info('do_POST: body:' + str(type(body)))\n", + " logger.info('do_POST: body:' + str(body))\n", + " data = json.loads(str(body))\n", + " logger.info('do_POST: data: %s', str(data))\n", + "\n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " got_notification = True\n", + " return\n", + " # End of function do_POST\n", + "\n", + " def do_PUT(self):\n", + " logger.info('>>> do_PUT: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_PUT: ' + ctype)\n", + "\n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " return\n", + " # End of function do_PUT\n", + "\n", + " def do_PATCH(self):\n", + " logger.info('>>> do_PATCH: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_PATCH: ' + ctype)\n", + " \n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " return\n", + " # End of function do_PATCH\n", + " # End of class HTTPRequestHandler\n", + "\n", + " def do_DELETE(self):\n", + " logger.info('>>> do_DELETE: ' + self.path)\n", + "\n", + " ctype = self.headers.get('content-type')\n", + " logger.info('do_DELETE: ' + ctype)\n", + " \n", + " self.send_response(HTTPStatus.NOT_IMPLEMENTED)\n", + " self.end_headers()\n", + " return\n", + " # End of function do_DELETE\n", + " # End of class HTTPRequestHandler\n", + "\n", + "def start_notification_server() -> HTTPServer:\n", + " \"\"\"\n", + " Start the notification server\n", + " :return The instance of the HTTP server\n", + " \"\"\"\n", + " global LISTENER_PORT, got_notification, logger\n", + "\n", + " logger.debug('>>> start_notification_server on port ' + str(LISTENER_PORT))\n", + " got_notification = False\n", + " server_address = ('', LISTENER_PORT)\n", + " httpd = HTTPServer(server_address, HTTPServer_RequestHandler)\n", + " # Start notification server in a daemonized thread\n", + " notification_server = threading.Thread(target = httpd.serve_forever, name='notification_server')\n", + " notification_server.daemon = True\n", + " notification_server.start()\n", + " return httpd\n", + " # End of function HTTPRequestHandler\n", + "\n", + "def stop_notification_server(httpd: HTTPServer):\n", + " \"\"\"\n", + " Stop the notification server\n", + " :param The instance of the HTTP server\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> stop_notification_server')\n", + " httpd.server_close()\n", + " httpd=None\n", + " # End of function HTTPRequestHandler\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def subscribe_for_user_events(sandbox_name: str) -> object:\n", + " \"\"\"\n", + " Subscriptions for notifications related to location.\n", + " :param sandbox_name: The MEC Sandbox instance to use\n", + " :return: The HTTP respone, the subscription ID and the resource URL on success, None otherwise\n", + " :see ETSI GS MEC 013 V3.1.1 Clause 7.5 Resource: user_subscriptions\n", + " \"\"\"\n", + " global MEC_PLTF, logger, service_api\n", + "\n", + " logger.debug('>>> subscribe_for_user_events: ' + sandbox_name)\n", + " try:\n", + " url = '/{sandbox_name}/{mec_pltf}//location/v3/subscriptions/users'\n", + " logger.debug('subscribe_for_user_events: url: ' + url)\n", + " path_params = {}\n", + " path_params['sandbox_name'] = sandbox_name\n", + " path_params['mec_pltf'] = MEC_PLTF\n", + " header_params = {}\n", + " # HTTP header `Accept`\n", + " header_params['Accept'] = 'application/json' # noqa: E501\n", + " # HTTP header `Content-Type`\n", + " header_params['Content-Type'] = 'application/json' # noqa: E501\n", + " # Body\n", + " dict_body = {}\n", + " dict_body['subscriptionType'] = 'UserLocationEventSubscription'\n", + " dict_body['callbackReference'] = CALLBACK_URI + '/mec013/v3/location' # FIXME To be parameterized\n", + " dict_body['address'] = '10.100.0.1' # FIXME To be parameterized\n", + " dict_body['clientCorrelator'] = \"12345\"\n", + " dict_body['locationEventCriteria'] = [ \"ENTERING_AREA_EVENT\", \"LEAVING_AREA_EVENT\"]\n", + " m = {}\n", + " m[\"userLocationEventSubscription\"] = dict_body\n", + " (result, status, headers) = service_api.call_api(url, 'POST', header_params=header_params, path_params = path_params, body=m, async_req=False)\n", + " return (result, extract_sub_id(headers['Location']), headers['Location'])\n", + " except ApiException as e:\n", + " logger.error('Exception when calling call_api: %s\\n' % e)\n", + " return (None, None, None)\n", + " # End of function subscribe_for_user_are_event" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Putting everything together\n", + "\n", + "It is time now to create the our third iteration of our MEC application.\n", + "\n", + "The sequence is the following:\n", + "- Login\n", + "- Activate a network scenario\n", + "- Create subscription\n", + "- Wait for notification\n", + "- Delete our application instance identifier\n", + "- Deactivate a network scenario\n", + "- Logout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our skeleton of our MEC application:\n", + " - Login\n", + " - Activate a network scenario\n", + " - Create subscription\n", + " - Wait for notification\n", + " - Delete our application instance identifier\n", + " - Deactivate a network scenario\n", + " - Logout\n", + " \"\"\" \n", + " global logger, nw_scenarios, got_notification\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Start notification server in a daemonized thread\n", + " httpd = start_notification_server()\n", + "\n", + " # Setup the MEC application\n", + " (sandbox_name, app_inst_id, sub_id) = mec_app_setup()\n", + "\n", + " # Create subscription\n", + " result, status, headers = subscribe_for_user_events(sandbox_name)\n", + " logger.info('UE location information: status: %s', str(status))\n", + " if status != 200:\n", + " logger.error('Failed to get UE location information')\n", + " else:\n", + " logger.info('UE location information: ' + str(result.data))\n", + " \n", + " # Getting UE location lookup\n", + " result, status = get_ue_location(sandbox_name, '10.100.0.1')\n", + " logger.info('UE location information: status: ' + str(status))\n", + " if status != 200:\n", + " logger.error('Failed to get UE location information')\n", + " else:\n", + " logger.info('UE location information: ' + str(result.data))\n", + "\n", + " # Wait for the notification\n", + " counter = 0\n", + " while not got_notification and counter < 30:\n", + " logger.info('Waiting for subscription...')\n", + " time.sleep(STABLE_TIME_OUT)\n", + " counter += 1\n", + " # End of 'while' statement\n", + "\n", + " # Stop notification server\n", + " stop_notification_server(httpd)\n", + "\n", + " # Terminate the MEC application\n", + " mec_app_termination(sandbox_name, app_inst_id, sub_id)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fith round: Update sensor value\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Annexes\n", + "\n", + "## Annex A: How to use an existing MEC sandbox instance\n", + "\n", + "This case is used when the MEC Sandbox API is not used. The procedure is the following:\n", + "- Log to the MEC Sandbox using a WEB browser\n", + "- Select a network scenario\n", + "- Create a new application instance\n", + "\n", + "When it is done, the newly created application instance is used by your application when required. This application instance is usually passed to your application in the command line or using a configuration file\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bibliography\n", + "\n", + "1. ETSI GS MEC 002 (V2.2.1) (01-2022): \"Multi-access Edge Computing (MEC); Phase 2: Use Cases and Requirements\".\n", + "2. ETSI GS MEC 033 V3.1.1 (2022-12): \"Multi-access Edge Computing (MEC); IoT APII\".\n", + "3. ETSI GS MEC 046V3.1.1 (2024-04): \"Multi-access Edge Computing (MEC); Sensor-sharing API\".s" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/demo9/python/notebook/images/V2X Predicted QoS.jpg b/examples/demo9/python/notebook/images/V2X Predicted QoS.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03a9b562a513afd148460fe210ead43657821bfd Binary files /dev/null and b/examples/demo9/python/notebook/images/V2X Predicted QoS.jpg differ diff --git a/examples/demo9/python/notebook/images/capif.png b/examples/demo9/python/notebook/images/capif.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf3593483ff14719e71b01332633845b057710b Binary files /dev/null and b/examples/demo9/python/notebook/images/capif.png differ diff --git a/examples/demo9/python/notebook/images/project_arch.jpg b/examples/demo9/python/notebook/images/project_arch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c1f7ac205ce79bd93df8486561143d506b1e60d Binary files /dev/null and b/examples/demo9/python/notebook/images/project_arch.jpg differ diff --git a/go-apps/meep-app-enablement/server/capif-mgmt/aef_profile_version.go b/go-apps/meep-app-enablement/server/capif-mgmt/aef_profile_version.go new file mode 100644 index 0000000000000000000000000000000000000000..ac7d5d8316337c9bde340428a52a18c87ca70038 --- /dev/null +++ b/go-apps/meep-app-enablement/server/capif-mgmt/aef_profile_version.go @@ -0,0 +1,15 @@ +/* + * MEC service management realized by CAPIF APIs + * + * The ETSI MEC ISG MEC011 MEC Service Management realized by CAPIF APIs described using OpenAPI + * + * API version: 3.2.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package server + +type Version struct { + APIVersion string `json:"apiVersion"` +} diff --git a/go-apps/meep-app-enablement/server/capif-mgmt/model_endpoint_info.go b/go-apps/meep-app-enablement/server/capif-mgmt/model_endpoint_info.go new file mode 100644 index 0000000000000000000000000000000000000000..7bb21c1cb0594bc8db30a0cbe50581a0c5947712 --- /dev/null +++ b/go-apps/meep-app-enablement/server/capif-mgmt/model_endpoint_info.go @@ -0,0 +1,26 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ + +package server + +// Address represents a (host, port) pair +type Address struct { + Host string `json:"host"` + Port int `json:"port"` +} + +// EndPointInfo represents transport endpoint information. +// Exactly one of uris, fqdn, addresses, or alternative must be present. +type EndPointInfo struct { + Uris []string `json:"uris,omitempty"` + Fqdn []string `json:"fqdn,omitempty"` + Addresses []Address `json:"addresses,omitempty"` + Alternative interface{} `json:"alternative,omitempty"` // flexible placeholder +} diff --git a/go-apps/meep-app-enablement/server/capif-mgmt/model_interface_description.go b/go-apps/meep-app-enablement/server/capif-mgmt/model_interface_description.go new file mode 100644 index 0000000000000000000000000000000000000000..920d627c989cf45b36ae68fc454b5b6f737c7698 --- /dev/null +++ b/go-apps/meep-app-enablement/server/capif-mgmt/model_interface_description.go @@ -0,0 +1,18 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +type InterfaceDescription struct { + Ipv4Addr *string `json:"ipv4Addr,omitempty"` + Ipv6Addr *string `json:"ipv6Addr,omitempty"` + Fqdn *string `json:"fqdn,omitempty"` + Port *int `json:"port,omitempty"` + ApiPrefix *string `json:"apiPrefix,omitempty"` +} diff --git a/go-apps/meep-cloud-mosquitto/Dockerfile b/go-apps/meep-cloud-mosquitto/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..49565dd49dd3537cd9e500659240c9d872995ea8 --- /dev/null +++ b/go-apps/meep-cloud-mosquitto/Dockerfile @@ -0,0 +1,18 @@ +FROM eclipse-mosquitto:latest + +# Install some libraries +RUN apk update && apk add curl jq + +RUN mkdir -p /run/mosquitto +# Create config and log directories +RUN mkdir -p /mosquitto/config/conf.d \ + && mkdir -p /mosquitto/data /mosquitto/log +# Copy the main mosquitto.conf +COPY data/mosquitto.conf /mosquitto/config/mosquitto.conf +# Copy additional listener config +COPY data/listener.conf /mosquitto/config/conf.d/listener.conf + +WORKDIR /usr/src/app +COPY ./data /usr/src/app +RUN chmod +x /usr/src/app/entrypoint.sh +ENTRYPOINT [ "/usr/src/app/entrypoint.sh" ] diff --git a/go-apps/meep-cloud-mosquitto/entrypoint.sh b/go-apps/meep-cloud-mosquitto/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..d293c5c7b4ba85e8eaa7ecae5d7b54697c625f8b --- /dev/null +++ b/go-apps/meep-cloud-mosquitto/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e +SERVICE_NAME="meep-cloud-mosquitto" +# Get the namespace from the service account +NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) +# Get the token from the service account +TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +echo "Token: $TOKEN" +# Query the Kubernetes API to get the NodePort for the service +NODE_PORT=$(curl -sSk \ + -H "Authorization: Bearer $TOKEN" \ + https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ + | jq -r '.spec.ports[0].nodePort') +echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT" +exec /usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf diff --git a/go-apps/meep-cloud-mosquitto/listener.conf b/go-apps/meep-cloud-mosquitto/listener.conf new file mode 100644 index 0000000000000000000000000000000000000000..40b246c155fb9f9f1ca71afbcca940c8772f5e2a --- /dev/null +++ b/go-apps/meep-cloud-mosquitto/listener.conf @@ -0,0 +1,9 @@ +# Raw TCP MQTT listener +listener 9001 +protocol mqtt +allow_anonymous true + +# WebSocket listener (plaintext) +listener 1883 +protocol websockets +allow_anonymous true diff --git a/go-apps/meep-cloud-mosquitto/mosquitto.conf b/go-apps/meep-cloud-mosquitto/mosquitto.conf new file mode 100644 index 0000000000000000000000000000000000000000..c6fc4ea1069b5b2bfd0f7a5e0b390bec266d04af --- /dev/null +++ b/go-apps/meep-cloud-mosquitto/mosquitto.conf @@ -0,0 +1,10 @@ +# Main Mosquitto config + +#pid_file /run/mosquitto/mosquitto.pid +persistence true +persistence_location /mosquitto/data/ +log_dest file /mosquitto/log/mosquitto.log + +log_type all + +include_dir /mosquitto/config/conf.d diff --git a/go-apps/meep-iot-pltf/meep-acme-in-cse/Dockerfile b/go-apps/meep-iot-pltf/meep-acme-in-cse/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1eab6208e3b4d24ce74ea80a388a305479161f8b --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-in-cse/Dockerfile @@ -0,0 +1,55 @@ +FROM ubuntu:24.04 + +ENV MQTT_ENABLE="" +ENV MQTT_HOST="" +ENV MQTT_PORT="" +ENV MQTT_USERNAME="" +ENV MQTT_PASSWORD="" +ENV SERVER_PORT="" +ENV SERVER_TYPE="" +ENV SERVER_IP="" +ENV ENABLE_COAP="" +ENV ENABLE_WS="" +ENV CSE_BASE_NAME="" +ENV CSE_BASE_RI="" +ENV MEEP_MEP_NAME="" +ENV MEC_SANDBOX_SERVER="" + +RUN echo "meep-acme-in-cse" > /etc/hostname \ + && DEBIAN_FRONTEND=noninteractive apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + curl \ + gettext \ + git \ + gnutls-bin \ + iputils-ping \ + jq \ + libedit2 \ + libffi-dev \ + libglib2.0-dev \ + libssl-dev \ + lsof \ + ntp \ + pkg-config \ + python3-dev \ + python3-pip \ + python3-setuptools \ + sudo \ + tzdata \ + && DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y \ + && DEBIAN_FRONTEND=noninteractive apt-get autoclean \ + && DEBIAN_FRONTEND=noninteractive apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/app + +RUN git clone --branch development https://github.com/ankraft/ACME-oneM2M-CSE.git ACME-oneM2M-CSE + +WORKDIR /usr/src/app/ACME-oneM2M-CSE + +COPY ./data /usr/src/app/ACME-oneM2M-CSE + +RUN chmod +x entrypoint.sh \ + && pip3 install --no-cache-dir -r requirements.txt --break-system-packages + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/go-apps/meep-iot-pltf/meep-acme-in-cse/acme.ini.in b/go-apps/meep-iot-pltf/meep-acme-in-cse/acme.ini.in new file mode 100644 index 0000000000000000000000000000000000000000..c434ab0ce34cba19317fb734e968d9554849a5bd --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-in-cse/acme.ini.in @@ -0,0 +1,77 @@ +; /home/etsi/frameworks/ACME-oneM2M-CSE/acme.ini +; +; Auto-generated configuration file for the [ACME] CSE. +; +; This file was created by the on-boarding process and can be modified manually by +; editing the values below, or by adding new sections to override the default values. +; +; The file is in the INI format. Lines starting with a semicolon (;) are comments. +; The configuration is divided into sections, each section starting with a section +; name in square brackets ([section.name]). The section name is followed by a list +; of key-value pairs, one per line, in the format key=value. The key and value are +; separated by an equal sign (=). +; +; created: 2024-11-21 06:28:46 +; +; CSE type: IN +; Environment: Development +; + +[basic.config] +cseType=$SERVER_TYPE +cseID=$CSE_BASE_RI +cseName=$CSE_BASE_NAME +adminID=CAdmin +networkInterface=0.0.0.0 +cseHost=$SERVER_IP +httpPort=$SERVER_PORT +databaseType=memory +logLevel=debug +consoleTheme=dark + +[cse] +poa=https://$SERVER_IP:443/$SVC_PATH + +[mqtt] +enable=$MQTT_ENABLE +address=$MQTT_HOST +keepalive=45 + +[mqtt.websocket] +enable=$MQTT_ENABLE +port=$MQTT_PORT +;transport=websockets +path=$WEBSOCK_ETPATH + +[mqtt.security] +useTLS=true + +[cse.registration] +; Edit this to add more allowed originators. +;allowedCSROriginators=/$CSE_BASE_RI,/acme-mep-id-mn-cse,/id-in,/id-mn,/id-asn,/mep-id-mn-cse +allowedCSROriginators=/* +;address=https://${basic.config:registrarCseHost}:${basic.config:registrarCsePort} + +[textui] +startWithTUI=false + +[cse.operation.requests] +enable=true + +[http] +enableUpperTesterEndpoint=true +enableStructureEndpoint=true +root=/ +address=https://${basic.config:cseHost}:${basic.config:httpPort}${http:externalRoot} +externalRoot=/$SVC_PATH/ + +[webui] +root=/webui + +[coap] +enable=$ENABLE_COAP +port=5683 + +[websocket] +enable=false +port=8180 diff --git a/go-apps/meep-iot-pltf/meep-acme-in-cse/entrypoint.sh b/go-apps/meep-iot-pltf/meep-acme-in-cse/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..b6a40717113b0a5105fc31a1346c30c33e38fc94 --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-in-cse/entrypoint.sh @@ -0,0 +1,114 @@ +#!/bin/bash +set -e + +echo "MEEP_HOST_URL: ${MEEP_HOST_URL}" +# SERVER_IP="${MEEP_HOST_URL#http://}"; MEEP_HOST_URL="${MEEP_HOST_URL#https://}" +SERVER_IP="${MEEP_HOST_URL#http://}" +SERVER_IP="${SERVER_IP#https://}" + +echo "MEC_PLATFORM=$MEC_PLATFORM" +echo "MEEP_SANDBOX_NAME=$MEEP_SANDBOX_NAME" + +# Other environment variables +echo "SERVER_TYPE: ${SERVER_TYPE}" +echo "SERVER_PORT: ${SERVER_PORT}" + +echo "CSE_BASE_NAME: ${CSE_BASE_NAME}" +echo "CSE_BASE_RI: ${CSE_BASE_RI}" + +echo "MQTT_ENABLE: ${MQTT_ENABLE}" +echo "MQTT_HOST: ${MQTT_HOST}" +echo "MQTT_PORT: ${MQTT_PORT}" +echo "MQTT_USERNAME: ${MQTT_USERNAME}" +echo "MQTT_PASSWORD: ${MQTT_PASSWORD}" + +# Retrieve the internal meep-mosquitto IP address +SERVICE_NAME="monaco-telecom-meep-cloud-mosquitto" +NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) +TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +# MOSQUITTO_NODE_IP_ADDRESS=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.clusterIP') +# echo "Internal IP exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $MOSQUITTO_NODE_IP_ADDRESS" + +# # Retrieve the internal meep-mosquitto port id +# MOSQUITTO_NODE_PORT=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.ports[0].targetPort') +# echo "Internal NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $MOSQUITTO_NODE_PORT" + +if [[ ! -z "${MEEP_MEP_NAME}" ]]; then + svcPath="${MEEP_SANDBOX_NAME}/${MEEP_MEP_NAME}" +else + svcPath="${MEEP_SANDBOX_NAME}" +fi + +if [ "${MEEP_HOST_URL}" == "" ] +then + echo "$0: MEEP_HOST_URL parameter is not set" + MEEP_HOST_URL=mec-platform.etsi.org +fi + +sleep 5 # Wait for ETSI MEC Platform up and stable + +SVC_PATH="${svcPath}/meep-acme-in-cse" + +SERVER_IP=`echo $SERVER_IP | sed -e "s/https:\/\///g"` # Remove https:// +SERVER_PORT=${SERVER_PORT:-3003} +MEC_SANDBOX_ID=${MEEP_SANDBOX_NAME:-"meep-sandbox"} + +CSE_BASE_NAME=${CSE_BASE_NAME:-"laboai-cse-in"} +CSE_BASE_RI=${CSE_BASE_RI:-"laboai-id-in"} +ENABLE_COAP=${ENABLE_COAP:-false} +ENABLE_WS=${ENABLE_WS:-false} + +# MQTT Configuration +MQTT_ENABLE=${MQTT_ENABLE:-true} +MQTT_HOST=${SERVER_IP:-"monaco-telecom-meep-cloud-mosquitto"} +MQTT_PORT=${MOSQUITTO_NODE_PORT:-443} +MQTT_USERNAME=${MQTT_USERNAME:-"acme-mn-cse"} +MQTT_PASSWORD=${MQTT_PASSWORD:-"mqtt"} + +WEBSOCK_ETPATH="/$MEC_SANDBOX_ID"${WEBSOCK_ETPATH:-"/monaco-telecom/meep-mosquitto"} + +echo "Environment variables set:" +echo "SERVER_TYPE: ${SERVER_TYPE}" +echo "SERVER_IP: ${SERVER_IP}" +echo "SERVER_PORT: ${SERVER_PORT}" +echo "CSE_BASE_NAME: ${CSE_BASE_NAME}" +echo "CSE_BASE_RI: ${CSE_BASE_RI}" +echo "ENABLE_COAP: ${ENABLE_COAP}" +echo "ENABLE_WS: ${ENABLE_WS}" +echo "MQTT_ENABLE: ${MQTT_ENABLE}" +echo "MQTT_HOST: ${MQTT_HOST}" +echo "MQTT_PORT: ${MQTT_PORT}" +echo "MQTT_USERNAME: ${MQTT_USERNAME}" +echo "MQTT_PASSWORD: ${MQTT_PASSWORD}" +echo "WEBSOCK_ETPATH: ${WEBSOCK_ETPATH}" +echo "SVC_PATH: ${SVC_PATH}" + +export SERVER_TYPE +export SERVER_IP +export SERVER_PORT + +export CSE_BASE_NAME +export CSE_BASE_RI +export ENABLE_COAP +export ENABLE_WS + +export MQTT_ENABLE +export MQTT_HOST +export MQTT_PORT +export MQTT_USERNAME +export MQTT_PASSWORD +export WEBSOCK_ETPATH + +export SVC_PATH + +workdir="/usr/src/app/ACME-oneM2M-CSE" +cd "$workdir" || { echo "Directory $workdir not found"; exit 1; } +envsubst < acme.ini.in > acme.ini +cat acme.ini +python3 -m acme diff --git a/go-apps/meep-iot-pltf/meep-acme-in-cse/requirements.txt b/go-apps/meep-iot-pltf/meep-acme-in-cse/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..aadaf282395d072f154c19e1732c2276e686ad9b --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-in-cse/requirements.txt @@ -0,0 +1,111 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile +# +blinker==1.9.0 + # via flask +cachetools==6.1.0 + # via acmecse-dev (setup.py) +cbor2==5.6.5 + # via acmecse-dev (setup.py) +certifi==2025.8.3 + # via requests +charset-normalizer==3.4.3 + # via requests +click==8.2.1 + # via flask +coapthon3-acme-cse==1.1.4 + # via acmecse-dev (setup.py) +flask==3.1.1 + # via + # acmecse-dev (setup.py) + # flask-cors +flask-cors==6.0.1 + # via acmecse-dev (setup.py) +idna==3.10 + # via requests +inquirerpy==0.3.4 + # via acmecse-dev (setup.py) +isodate==0.7.2 + # via acmecse-dev (setup.py) +itsdangerous==2.2.0 + # via flask +jinja2==3.1.6 + # via flask +kazoo==2.10.0 + # via acmecse-dev (setup.py) +linkify-it-py==2.0.3 + # via markdown-it-py +markdown-it-py[linkify,plugins]==4.0.0 + # via + # mdit-py-plugins + # rich + # textual +markupsafe==3.0.2 + # via + # flask + # jinja2 + # werkzeug +mdit-py-plugins==0.5.0 + # via markdown-it-py +mdurl==0.1.2 + # via markdown-it-py + +paho-mqtt==2.1.0 + # via acmecse-dev (setup.py) +pfzy==0.3.4 + # via inquirerpy +platformdirs==4.3.8 + # via textual +plotext==5.3.2 + # via + # acmecse-dev (setup.py) + # textual-plotext +prompt-toolkit==3.0.51 + # via inquirerpy +psycopg2-binary==2.9.10 + # via acmecse-dev (setup.py) +pygments==2.19.2 + # via + # rich + # textual +pyparsing==3.2.3 + # via rdflib +pyperclip==1.9.0 + # via acmecse-dev (setup.py) +rdflib==7.1.4 + # via acmecse-dev (setup.py) +requests==2.32.4 + # via acmecse-dev (setup.py) +rich==14.1.0 + # via + # acmecse-dev (setup.py) + # textual +shapely==2.1.1 + # via acmecse-dev (setup.py) +textual==5.2.0 + # via + # acmecse-dev (setup.py) + # textual-plotext +textual-plotext==1.0.1 + # via acmecse-dev (setup.py) +tinydb==4.8.2 + # via acmecse-dev (setup.py) +typing-extensions==4.14.1 + # via textual +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.5.0 + # via requests +waitress==3.0.2 + # via acmecse-dev (setup.py) +wcwidth==0.2.13 + # via prompt-toolkit +websockets==15.0.1 + # via acmecse-dev (setup.py) +werkzeug==3.1.3 + # via + # flask + # flask-cors diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/Dockerfile b/go-apps/meep-iot-pltf/meep-acme-mn-cse/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7ef8c3035259cd6c5ce584ccf0ec79b740b8be06 --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/Dockerfile @@ -0,0 +1,63 @@ +FROM ubuntu:24.04 + +ENV SERVER_TYPE="" +ENV SERVER_IP="" +ENV SERVER_PORT="" +ENV CSE_BASE_NAME="" +ENV CSE_BASE_RI="" +ENV REMOTE_CSE_ID="" +ENV REMOTE_CSE_NAME="" +ENV REMOTE_CSE_HOST="" +ENV REMOTE_CSE_PORT="" +ENV MQTT_ENABLE="" +ENV MQTT_HOST="" +ENV MQTT_PORT="" +ENV MQTT_USERNAME="" +ENV MQTT_PASSWORD="" +ENV MEC_ENABLE="" +ENV MEC_HOST_URL="" +ENV MEC_PLATFORM="" +ENV MEC_SANDBOX_ID="" +ENV ENABLE_COAP="" +ENV ENABLE_WS="" +ENV ACME_IN_SERVICE_NAME="" + +RUN echo "meep-acme-mn-cse" > /etc/hostname \ + && DEBIAN_FRONTEND=noninteractive apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + curl \ + gettext \ + git \ + gnutls-bin \ + iputils-ping \ + jq \ + libedit2 \ + libffi-dev \ + libglib2.0-dev \ + libssl-dev \ + lsof \ + ntp \ + pkg-config \ + python3-dev \ + python3-pip \ + python3-setuptools \ + sudo \ + tzdata \ + && DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y \ + && DEBIAN_FRONTEND=noninteractive apt-get autoclean \ + && DEBIAN_FRONTEND=noninteractive apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/app + +RUN git clone --branch development https://github.com/ankraft/ACME-oneM2M-CSE.git ACME-oneM2M-CSE + +WORKDIR /usr/src/app/ACME-oneM2M-CSE + +COPY ./data /usr/src/app/ACME-oneM2M-CSE + +RUN chmod +x entrypoint.sh \ + && pip3 install --no-cache-dir -r requirements.txt --break-system-packages + +ENTRYPOINT ["./entrypoint.sh"] + diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme.ini.in b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme.ini.in new file mode 100644 index 0000000000000000000000000000000000000000..5115ca606092868df3160696a892e44ebb3669b2 --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme.ini.in @@ -0,0 +1,101 @@ +; /home/etsi/dev/etsi-mec-sandbox/go-apps/meep-iot-pltf/acme-dev/acme.ini +; +; Auto-generated configuration file for the [ACME] CSE. +; +; This file was created by the on-boarding process and can be modified manually by +; editing the values below, or by adding new sections to override the default values. +; +; The file is in the INI format. Lines starting with a semicolon (;) are comments. +; The configuration is divided into sections, each section starting with a section +; name in square brackets ([section.name]). The section name is followed by a list +; of key-value pairs, one per line, in the format key=value. The key and value are +; separated by an equal sign (=). +; +; created: 2025-06-19 12:46:05 +; +; CSE type: MN +; Environment: Development +; + +[basic.config] +cseType=$SERVER_TYPE +cseID=$CSE_BASE_RI +cseName=$CSE_BASE_NAME +adminID=CAdmin +networkInterface=0.0.0.0 +cseHost=$SERVER_IP +httpPort=$SERVER_PORT +secret=1234 +registrarCseID=$REMOTE_CSE_ID +registrarCseName=$REMOTE_CSE_NAME +registrarCseHost=$REMOTE_CSE_HOST +registrarCsePort=$REMOTE_CSE_PORT +databaseType=memory +logLevel=debug +consoleTheme=dark + +[cse] +poa=https://$SERVER_IP:443/$SVC_PATH + +[cse.registration] +; Edit this to add more allowed originators. +;allowedCSROriginators=/$REMOTE_CSE_ID,/laboai-id-in,/laboai-cse-in,/acme-mep-id-mn-cse,/id-in,/id-mn,/id-asn,/mep-id-mn-cse +allowedCSROriginators=/* + +[cse.registrar] +INCSEcseID=/$REMOTE_CSE_ID +address=https://${basic.config:registrarCseHost}:${basic.config:registrarCsePort}/ +root=$REMOTE_SVC_PATH + +[textui] +startWithTUI=false + +[cse.operation.requests] +enable=true + +[http] +enableUpperTesterEndpoint = true +enableStructureEndpoint = true +root=/ +address=https://${basic.config:cseHost}:${basic.config:httpPort}${http:externalRoot} +externalRoot=/$SVC_PATH/ + +[webui] +root=/webui + +[mqtt] +enable=$MQTT_ENABLE +address=$MQTT_HOST +keepalive=45 + +[mqtt.websocket] +enable=$MQTT_ENABLE +port=$MQTT_PORT +;transport=websockets +path=$WEBSOCK_ETPATH + +[mqtt.security] +useTLS=true + +[coap] +enable=$ENABLE_COAP +port=5683 + +[websocket] +enable=false +port=8180 + +[logging] +enableBindingsLogging = false + +[etsi_mec] +mec_enable=$MEC_ENABLE +mec_host=$MEC_HOST_URL +mec_port=443 +mec_protocol=https +mec_platform=$MEC_PLATFORM +mec_sandbox_id=$MEC_SANDBOX_ID +cse_external_ip=$CSE_EXTERNAL_IP +cse_external_port=$CSE_EXTERNAL_PORT +fullAddress=$MN_CSE_FULL_ADDRESS +mec_app_instance_id=$MEC_APP_INSTANCE_ID \ No newline at end of file diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/init/mec-dev.fcp b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/init/mec-dev.fcp new file mode 100644 index 0000000000000000000000000000000000000000..515703ef55cdfbc123e05f16a265378babc0f7f7 --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/init/mec-dev.fcp @@ -0,0 +1,43 @@ +// Created by SDTTool +// oneM2M Release: 4.12.0 +// Domain: org.onem2m.common +[ + + // MecDeviceClass: iotDevice + { + "type" : "mec:iotDev", + "lname" : "iotDevice", + "sdttype" : "mecdevice", + "cnd" : "org.onem2m.common.mec.device.iotDevice", + "attributes": [ + // Property: deviceId + { "sname" : "devId", "lname" : "deviceId", "type" : "string", "car" : "01", "oc" : "O" }, //0 Optional, 1..N, cardinality, oc: OnCreate Mandatory, Update, Optional, Np: Not provisioned + // Property: gspi + { "sname" : "gspi", "lname" : "gspi", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: pei + { "sname" : "mpei", "lname" : "mec-pei", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: supi + { "sname" : "supi", "lname" : "supi", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: msisdn + { "sname" : "msisdn", "lname" : "msisdn", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: imei + { "sname" : "imei", "lname" : "imei", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: imsi + { "sname" : "imsi", "lname" : "imsi", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: iccid + { "sname" : "iccid", "lname" : "iccid", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: requestedIotPlatformId + { "sname" : "rIPId", "lname" : "requestedIotPlatformId", "type" : "string", "car" : "01" }, + // Property: requestedUserTransportId + { "sname" : "rUTId", "lname" : "requestedUserTransportId", "type" : "string", "car" : "01", "oc" : "O" }, + // Property: requestedMecTrafficRule + { "sname" : "rMTRe", "lname" : "requestedMecTrafficRule", "type" : "list", "car" : "01", "oc" : "O" }, + // Property: enabled + { "sname" : "enabd", "lname" : "enabled", "type" : "boolean", "car" : "1", "oc" : "M" } + ], + "children" : [ + // Module class + ] + } + +] diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/protocols/MECClient.py b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/protocols/MECClient.py new file mode 100644 index 0000000000000000000000000000000000000000..08008b05350a1525738147c9644b086f4c16118c --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/protocols/MECClient.py @@ -0,0 +1,388 @@ +# +# MECClient.py +# +# (c) 2021 by Andreas Kraft +# License: BSD 3-Clause License. See the LICENSE file for further details. +# +""" Implementation of an MEC Client for an MEC Mcx binding implementation. +""" + +from __future__ import annotations +from typing import Tuple, cast, Dict, Optional, Any + +import time +import flask +from flask import Flask, Request, request + +from werkzeug.wrappers import Response +from werkzeug.serving import WSGIRequestHandler +from werkzeug.datastructures import MultiDict as WerkzeugMultiDict +from waitress import serve +from flask_cors import CORS +import requests +import isodate + +from urllib.parse import urlparse, unquote + +from ..etc.Types import Result, ResponseStatusCode +from ..runtime.Configuration import Configuration + +from ..runtime.Logging import Logging as L + +import uuid + +import json + +class MECClient(object): + """ The general MEC manager for this CSE. + """ + # TODO doc + + __slots__ = ( + 'http_address', + 'http_port', + 'cse_external_ip', + 'cse_external_port', + 'use_wss', + 'mqtt_address', + 'mqtt_port', + 'wss_path', + 'cse_resourceID', + 'fullAddress', + 'isStopped', + 'mec_app_instance_id', + 'mecConnection', + 'mecConnections', + 'isInfo', + 'headers', + 'register' + ) + """ Slots for the MECClient. """ + + # TODO move config handling to event handler + + def __init__(self, p_http_address: str, p_http_port: str, p_cse_resourceID: str, + p_cse_external_ip: str, p_cse_external_port: str, p_use_wss: bool, p_mqtt_address: str, + p_mqtt_port: str, p_fullAddress: str, p_mec_app_instance_id: str , p_wss_path: str = "") -> None: + """ Initialize the MEC client. + """ + self.http_address = p_http_address + u = urlparse(self.http_address) + self.http_address = u.hostname + self.http_port = p_http_port + self.cse_external_ip = p_cse_external_ip + self.cse_external_port = p_cse_external_port + self.cse_resourceID = p_cse_resourceID + self.use_wss = p_use_wss + self.mqtt_address = p_mqtt_address + self.mqtt_port = p_mqtt_port + self.wss_path = p_wss_path + self.fullAddress = p_fullAddress + self.mec_app_instance_id = p_mec_app_instance_id + + self.isStopped = False + """ Flag to indicate whether the MEC client is stopped. """ + + self.mecConnections:Dict[Tuple[str, int], Flask] = {} + """ Dictionary of MEC connections. """ + + self.mecConnection = self.connectToMec(host = Configuration.mec_host, port = Configuration.mec_port) + """ The MEC connection. """ + + self.headers = {} + self.headers['Content-Type'] = 'application/json; charset=UTF-8' + + self.register = None + + L.isInfo and L.log('MEC Client initialized') + L.isInfo and L.log('MEC Client: self.http_address=' + str(self.http_address)) + + def run(self) -> bool: + """ Initialize and run the MEC client as a BackgroundWorker/Actor. + """ + if not Configuration.mec_enable or not self.mecConnection: + L.isInfo and L.log('MEC: client NOT enabled') + return True + L.isInfo and L.log('Start MEC client') + self.mecConnection.run() + #if not self.isFullySubscribed(): # This waits until the MEC Client connects and fully subscribes (until a timeout) + # return False + return True + + + def shutdown(self) -> bool: + """ Shutdown the MECClient. + """ + for id in list(self.mecConnections): + self.disconnectFromMec(id[0], id[1]) # 0 = host, 1 = port + self.mecConnection = None + L.isInfo and L.log('MEC client shut down') + return True + + + def configUpdate(self, name:str, + key:Optional[str] = None, + value:Optional[Any] = None) -> None: + """ Callback for the `configUpdate` event. + + Args: + name: Event name. + key: Name of the updated configuration setting. + value: New value for the config setting. + """ + if key not in [ 'mec.host', + 'mec.enable', + 'mec.port', + 'mec.protocol', + 'mec.mec_platform', + 'mec.mec_sandbox_id' + ]: + return + + # possibly restart MEC client + self.shutdown() + self.run() + + + def pause(self) -> None: + """ Stop handling requests. + """ + L.isInfo and L.log('MecClient paused') + self.isStopped = True + + + def unpause(self) -> None: + """ Continue handling requests. + """ + L.isInfo and L.log('MecClient unpaused') + self.deregisterFromMec() + L.isInfo and L.log('MecClient deregistering done') + self.registerToMec() + L.isInfo and L.log('MecClient re-registering done') + self.isStopped = False + + + # + # Additional methods + # + + def connectToMec(self, host:str, port:int) -> Optional[Flask]: + if Configuration.mec_enable: + if not (mecConnect := self.mecConnections.get( (host, port) )): + mecConnection = Flask(host) + if mecConnection: + self.mecConnections[(host, port)] = mecConnection + return mecConnection + return None + + + def disconnectFromMec(self, host:str, port:int) -> None: + """ Remove the appropriate MECConnection for *host* and *port* from the + connection cache and also shut-down the connection. + """ + if (mecConnection := self.getMec(host, port)): + if self.register is not None: + self.deregisterFromMec() + del self.mecConnections[ (host, port) ] + # FIXME FSCOM Terminate Flask listener thread + + + def getMec(self, host:str, port:int) -> Flask: + """ Return the MECConnection for the *host* and *port* from the internal + connection cache. + """ + return self.mecConnections.get( (host, port) ) + + + ######################################################################### + # + # Send MEC requests + # + + def registerToMec(self) -> Result: + """ Register to MEC platform. + """ + + if self.isStopped: + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, + dbg = 'MEC client is not running') + + try: + url = \ + Configuration.mec_protocol + '://' + Configuration.mec_host + ':' + str(Configuration.mec_port) + \ + '/' + Configuration.mec_sandbox_id + \ + '/' + Configuration.mec_platform + \ + '/iots/v1/registered_iot_platforms' + L.isInfo and L.log('MEC Client: registerToMec=' + url) + body_json = {} + body_json['iotPlatformId'] = str(uuid.uuid4()) + userTransportInfoList = [] + userTransportInfo = {} + userTransportInfo['id'] = str(uuid.uuid4()) + userTransportInfo['name'] = Configuration.cse_cseID + userTransportInfo['type'] = 'MB_TOPIC_BASED' + if self.use_wss: + userTransportInfo['description'] = 'MQTT over WS-Secured' + userTransportInfo['protocol'] = 'MQTT+WSS' + else: + userTransportInfo['description'] = 'MQTT' + userTransportInfo['protocol'] = 'MQTT' + userTransportInfo['version'] = '2' + userTransportInfo['endpoint'] = {} + # userTransportInfo['endpoint']['addresses'] = [] + # The MEC API expects an array of URIs for the endpoint 'uris' field. + # Previously this was set to a single string which caused the server + # to return: "cannot unmarshal string into Go struct field ... uris of type []string". + if self.use_wss: + userTransportInfo['endpoint']['uris'] = [] + userTransportInfo['endpoint']['uris'].append('wss://' + self.mqtt_address + ':' + str(self.mqtt_port) + self.wss_path) + else: + userTransportInfo['endpoint']['addresses'] = [] + address = {} + address['host'] = self.mqtt_address + address['port'] = self.mqtt_port + userTransportInfo['endpoint']['addresses'].append(address) + # userTransportInfo['endpoint']['uris'] = [ self.fullAddress ] + # address = {} + # address['host'] = self.mqtt_address + # address['port'] = self.mqtt_port + # userTransportInfo['endpoint']['addresses'].append(address) + userTransportInfo['security'] = {} + userTransportInfo['implSpecificInfo'] = {} + userTransportInfoList.append(userTransportInfo) + body_json['userTransportInfo'] = userTransportInfoList + customServicesTransportInfoList = [] + customServicesTransportInfo = {} + # customServicesTransportInfo['id'] = str(uuid.uuid4()) + customServicesTransportInfo['id'] = Configuration.cse_cseID + customServicesTransportInfo['name'] = Configuration.cse_resourceName + customServicesTransportInfo['description'] = 'ACME oneM2M CSE ID' + customServicesTransportInfo['type'] = 'REST_HTTP' + customServicesTransportInfo['protocol'] = 'REST_HTTP' + customServicesTransportInfo['version'] = '4' + customServicesTransportInfo['endpoint'] = {} + if self.fullAddress == '': + customServicesTransportInfo['endpoint']['uris'] = [ 'https://' + self.cse_external_ip + ':' + str(self.http_port) ] + else: + customServicesTransportInfo['endpoint']['uris'] = [ self.fullAddress ] + customServicesTransportInfo['security'] = {} + customServicesTransportInfoList.append(customServicesTransportInfo) + body_json['customServicesTransportInfo'] = customServicesTransportInfoList + body_json['enabled'] = True + L.isInfo and L.log('MEC Client: body_json=' + str(body_json)) + + tries = 0 + while tries < 5: + response = requests.post(url, headers=self.headers, json=body_json, verify=False) + L.isInfo and L.log(f'MECClient.registerToMec: response.content: ' + str(response.content)) + if response.status_code == 201: + self.register = Result() + self.register.rsc = response.status_code + self.register.data = response.content + return self.register + else: + L.logErr(f'MECClient.registerToMec: ' + str(response.status_code)) + tries += 1 # Continue the loop + time.sleep(10) + except Exception as e: + L.logErr(f'MECClient.registerToMec: ' + str(e)) + + return None + + def deregisterFromMec(self) -> Result: + """ Deregister to MEC platform. + """ + if self.isStopped: + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC client is not running') + + if self.register is not None: + try: + json_dict = json.loads(self.register.data.decode('utf-8')) + url = \ + Configuration.mec_protocol + '://' + Configuration.mec_host + ':' + str(Configuration.mec_port) + \ + '/' + Configuration.mec_sandbox_id + \ + '/' + Configuration.mec_platform + \ + '/iots/v1/registered_iot_platforms/' + json_dict['iotPlatformId'] + response = requests.delete(url, headers=self.headers, verify=False) + self.register = None + return Result(rsc = response.status_code) + except Exception as e: + L.logErr(f'MECClient.deregisterToMec: ' + str(e)) + + self.register = None + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC Deregistration failure') + + def confirm_readyToMec(self) -> Result: + """ Confirm readiness to MEC platform. + """ + if self.isStopped: + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC client is not running') + + try: + req_body = { + "indication": "READY" + } + url = \ + Configuration.mec_protocol + '://' +Configuration.mec_host + ':' + str(Configuration.mec_port) + \ + '/' + Configuration.mec_sandbox_id + \ + '/' + Configuration.mec_platform + \ + '/mec_app_support/v2/applications/' + self.mec_app_instance_id + '/confirm_ready' + L.log('MECClient.confirm_readyToMec: ' + str(req_body)) + L.log('MECClient.confirm_readyToMec: ' + str(url)) + response = requests.post(url, headers=self.headers, json=req_body, verify=False) + return Result(rsc = response.status_code) + except Exception as e: + L.logErr(f'MECClient.confirm_readyToMec: ' + str(e)) + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC Confirm readiness failure') + + def post_mec_service(self) -> Result: + """ + Declare MEC service to MEC platform. + This function declares MN-CSE as MEC service to the MEC platform. + """ + if self.isStopped: + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC client is not running') + + try: + req_body = { + "serName": Configuration.cse_resourceName, + "serCategory": { + "href": "catalogueHref", + "id": Configuration.cse_cseID, + "name": Configuration.cse_resourceName, + "version": "v1" + }, + "version": "v1.0.0", + "state": "ACTIVE", + "transportInfo": { + "id": "acme-mn-cse-transport", + "name": "REST", + "type": "REST_HTTP", + "protocol": "HTTPS", + "version": "4.0", + "endpoint": { + "uris": [ + self.fullAddress + ], + "fqdn": None, + "addresses": None, + "alternative": None + }, + "security": None + }, + "serializer": "JSON", + "scopeOfLocality": "MEC_SYSTEM", + "consumedLocalOnly": True + } + url = \ + Configuration.mec_protocol + '://' + Configuration.mec_host + ':' + str(Configuration.mec_port) + \ + '/' + Configuration.mec_sandbox_id + \ + '/' + Configuration.mec_platform + \ + '/mec_service_mgmt/v1/applications/' + self.mec_app_instance_id + '/services' + L.log('MECClient.POST_mec_service: ' + str(req_body)) + L.log('MECClient.POST_mec_service: ' + str(url)) + response = requests.post(url, headers=self.headers, json=req_body, verify=False) + return Result(rsc = response.status_code) + except Exception as e: + L.logErr(f'MECClient.POST_mec_service: ' + str(e)) + return Result(rsc = ResponseStatusCode.INTERNAL_SERVER_ERROR, dbg = 'MEC Declare MEC service failure') \ No newline at end of file diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/CSE.py b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/CSE.py new file mode 100644 index 0000000000000000000000000000000000000000..31a68c71a26d583f2ee733e26b21feb4b732d77b --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/CSE.py @@ -0,0 +1,507 @@ +# +# CSE.py +# +# (c) 2020 by Andreas Kraft +# License: BSD 3-Clause License. See the LICENSE file for further details. +# +# Container that holds references to instances of various managing entities. +# + +""" This module implements various functions for CSE startip, running, resetting, shutdown etc. + It also provides various global variable that hold fixed configuration values. + In addition is holds pointers to the various runtime instance of CSE modules, packages etc. +""" + +from __future__ import annotations +from typing import Dict, Any, cast + +import atexit, argparse, sys, platform, os, signal, platform +from threading import Lock + +from ..helpers.BackgroundWorker import BackgroundWorkerPool +from ..etc.Constants import Constants as C, RuntimeConstants as RC +from ..etc.DateUtils import waitFor +from ..etc.Utils import runsInIPython +from ..etc.Types import CSEStatus, LogLevel +from ..etc.Constants import RuntimeConstants as RC +from ..etc.ResponseStatusCodes import ResponseException +from ..services.ActionManager import ActionManager +from ..runtime.Configuration import Configuration +from ..runtime.Console import Console + + +from ..services.Dispatcher import Dispatcher +from ..services.RequestManager import RequestManager +from .EventManager import EventManager +from ..services.GroupManager import GroupManager +from ..runtime.Importer import Importer +from ..services.LocationManager import LocationManager +from ..services.NotificationManager import NotificationManager +from ..runtime.PluginManager import PluginManager +from ..services.RegistrationManager import RegistrationManager +from ..services.RemoteCSEManager import RemoteCSEManager +from ..runtime.ScriptManager import ScriptManager +from ..services.SecurityManager import SecurityManager +from ..services.SemanticManager import SemanticManager +from ..runtime.Statistics import Statistics +from ..runtime.Storage import Storage +from ..runtime.TextUI import TextUI +from ..services.TimeManager import TimeManager +from ..services.TimeSeriesManager import TimeSeriesManager +from ..services.Validator import Validator +from ..protocols.HttpServer import HttpServer +from ..protocols.CoAPServer import CoAPServer +from ..protocols.MQTTClient import MQTTClient +from ..protocols.WebSocketServer import WebSocketServer +from ..services.AnnouncementManager import AnnouncementManager +from ..runtime.Logging import Logging as L +from ..protocols.MECClient import MECClient + +############################################################################## + +# singleton main components. These variables will hold all the various manager +# components that are used throughout the CSE implementation. + +action:ActionManager = None +""" Runtime instance of the `ActionManager`. """ + +announce:AnnouncementManager = None +""" Runtime instance of the `AnnouncementManager`. """ + +coapServer:CoAPServer = None +""" Runtime instance of the `CoAPServer`. """ + +console:Console = None +""" Runtime instance of the `Console`. """ + +dispatcher:Dispatcher = None +""" Runtime instance of the `Dispatcher`. """ + +event:EventManager = None +""" Runtime instance of the `EventManager`. """ + +groupResource:GroupManager = None +""" Runtime instance of the `GroupManager`. """ + +httpServer:HttpServer = None +""" Runtime instance of the `HttpServer`. """ + +importer:Importer = None +""" Runtime instance of the `Importer`. """ + +location:LocationManager = None +""" Runtime instance of the `LocationManager`. """ + +mqttClient:MQTTClient = None +""" Runtime instance of the `MQTTClient`. """ + +notification:NotificationManager = None +""" Runtime instance of the `NotificationManager`. """ + +pluginManager:PluginManager = None +""" Runtime instance of the `PluginManager`. """ + +registration:RegistrationManager = None +""" Runtime instance of the `RegistrationManager`. """ + +remote:RemoteCSEManager = None +""" Runtime instance of the `RemoteCSEManager`. """ + +request:RequestManager = None +""" Runtime instance of the `RequestManager`. """ + +script:ScriptManager = None +""" Runtime instance of the `ScriptManager`. """ + +security:SecurityManager = None +""" Runtime instance of the `SecurityManager`. """ + +semantic:SemanticManager = None +""" Runtime instance of the `SemanticManager`. """ + +statistics:Statistics = None +""" Runtime instance of the `Statistics`. """ + +storage:Storage = None +""" Runtime instance of the `Storage`. """ + +textUI:TextUI = None +""" Runtime instance of the `TextUI`. """ + +time:TimeManager = None +""" Runtime instance of the `TimeManager`. """ + +timeSeries:TimeSeriesManager = None +""" Runtime instance of the `TimeSeriesManager`. """ + +validator:Validator = None +""" Runtime instance of the `Validator`. """ + +webSocketServer:WebSocketServer = None +""" Runtime instance of the `WebSocketServer`. """ + + +mecClient:MECClient = None +""" Runtime instance of the `MECClient`. """ + + +# Global variables to hold various (configuation) values. + +_cseResetLock = Lock() +""" Internal CSE's lock when resetting. """ + +############################################################################## + + +def startup(args:argparse.Namespace, **kwargs:Dict[str, Any]) -> bool: + """ Startup of the CSE. Initialization of various global variables, creating and initializing of manager instances etc. + + Args: + args: Startup command line arguments. + kwargs: Optional, additional keyword arguments which are added as attributes to the *args* object. + Return: + False if the CSE couldn't initialized and started. + """ + global action, announce, coapServer, console, dispatcher, event, groupResource, httpServer, importer, location, mqttClient, mecClient + global notification, pluginManager, registration, remote, request, script, security, semantic + global statistics, storage, textUI, time, timeSeries, validator, webSocketServer + + # Set status + RC.cseStatus = CSEStatus.STARTING + + # Handle command line arguments and load the configuration + if not args: + args = argparse.Namespace() # In case args is None create a new args object and populate it + args.configfile = None + args.resetdb = False + args.loglevel = None + args.headless = False + for key, value in kwargs.items(): + args.__setattr__(key, value) + + event = EventManager() # Initialize the event manager before anything else + + if not Configuration.init(args): + RC.cseStatus = CSEStatus.STOPPED + return False + + # + # init Logging + # + L.init() + L.queueOff() # No queuing of log messages during startup + L.log('Starting CSE') + L.log(f'CSE-Type: {RC.cseType.name}') + if args.printconfig: + for l in Configuration.print().split('\n'): + L.log(l) + + # set the logger for the backgroundWorkers. Add an offset to compensate for + # this and other redirect functions to determine the correct file / linenumber + # in the log output + BackgroundWorkerPool.setLogger(lambda l,m: L.logWithLevel(l, m, stackOffset = 2)) + BackgroundWorkerPool.setJobBalance( balanceTarget = Configuration.cse_operation_jobs_balanceTarget, + balanceLatency = Configuration.cse_operation_jobs_balanceLatency, + balanceReduceFactor = Configuration.cse_operation_jobs_balanceReduceFactor) + + try: + textUI = TextUI() # Start the textUI + console = Console() # Start the console + storage = Storage() # Initialize the resource storage + + importer = Importer() # Initialize the importer + importer.importResourcePolicies() # Before initializing other components, import the resource policies + + statistics = Statistics() # Initialize the statistics system + registration = RegistrationManager() # Initialize the registration manager + validator = Validator() # Initialize the resource validator + dispatcher = Dispatcher() # Initialize the resource dispatcher + request = RequestManager() # Initialize the request manager + security = SecurityManager() # Initialize the security manager + httpServer = HttpServer() if not httpServer else httpServer # Initialize the HTTP server + coapServer = CoAPServer() # Initialize the CoAP server + mqttClient = MQTTClient() # Initialize the MQTT client + webSocketServer = WebSocketServer() # Initialize the WebSocket server + notification = NotificationManager() # Initialize the notification manager + groupResource = GroupManager() # Initialize the group manager + timeSeries = TimeSeriesManager() # Initialize the timeSeries manager + remote = RemoteCSEManager() # Initialize the remote CSE manager + announce = AnnouncementManager() # Initialize the announcement manager + semantic = SemanticManager() # Initialize the semantic manager + location = LocationManager() # Initialize the location manager + time = TimeManager() # Initialize the time mamanger + script = ScriptManager() # Initialize the script manager + action = ActionManager() # Initialize the action manager + + # Import attribute, flexContainer and enum policies, and configuration documentation + # + # After this, the CSE reads the scripts from the default and runtime init directories. + # It also runs the init script, if there is one. + # + + + if Configuration.mec_enable: + L.log('Initializing MEC client') + if Configuration.mqtt_websocket_enable: + mecClient = MECClient(Configuration.http_address, Configuration.http_port, Configuration.cse_resourceID, Configuration.cse_external_ip, Configuration.cse_external_port, Configuration.mqtt_websocket_enable, Configuration.mqtt_address, Configuration.mqtt_websocket_port, Configuration.fullAddress, Configuration.mec_app_instance_id, Configuration.mqtt_websocket_path) + else: + mecClient = MECClient(Configuration.http_address, Configuration.http_port, Configuration.cse_resourceID, Configuration.cse_external_ip, Configuration.cse_external_port, Configuration.mqtt_websocket_enable, Configuration.mqtt_address, Configuration.mqtt_port, Configuration.fullAddress, Configuration.mec_app_instance_id) + if mecClient is None: + Configuration.mec_enable = False + + # When this fails, we cannot continue with the CSE startup + if not importer.importPolicies() or not importer.importScripts(): + RC.cseStatus = CSEStatus.STOPPED + return False + + # Initialize the plugin manager + # This loads, configures and runs the plugins as well + pluginManager = PluginManager() + + + # Start the HTTP server + if not httpServer.run(): # This does return (!) + L.logErr('Terminating', showStackTrace = False) + RC.cseStatus = CSEStatus.STOPPED + return False + + # Start the CoAP server + if not coapServer.run(): # This does return + L.logErr('Terminating', showStackTrace = False) + RC.cseStatus = CSEStatus.STOPPED + return False + + # Start the MQTT client + if not mqttClient.run(): # This does return + L.logErr('Terminating', showStackTrace = False) + RC.cseStatus = CSEStatus.STOPPED + return False + + # Start the WebSocket server + if not webSocketServer.run(): # This does return + L.logErr('Terminating', showStackTrace = False) + RC.cseStatus = CSEStatus.STOPPED + return False + + except ResponseException as e: + L.logErr(f'Error during startup: {e.dbg}') + RC.cseStatus = CSEStatus.STOPPED + return False + except KeyError as e: + L.logErr(f'Error during startup: {e}') + RC.cseStatus = CSEStatus.STOPPED + return False + + except Exception as e: + L.logErr(f'Error during startup: {e}', exc=e) + RC.cseStatus = CSEStatus.STOPPED + return False + + # Enable log queuing + L.queueOn() + + + # Give the CSE a moment (2s) to experience fatal errors before printing the start message + + def _startUpFinished() -> None: + """ Internal function to print the CSE startup message after a delay + """ + RC.cseStatus = CSEStatus.RUNNING + # Send an event that the CSE startup finished + event.cseStartup() # type: ignore + + L.console('CSE started') + L.log('CSE started') + + BackgroundWorkerPool.newActor(_startUpFinished, delay = C.cseStartupDelay if RC.isHeadless else C.cseStartupDelay / 2.0, name = 'Delayed_startup_message' ).start() + + return True + + +def shutdown() -> None: + """ Gracefully shutdown the CSE programmatically. This will end the main console loop + to terminate. + + The actual shutdown happens in the _shutdown() method. + """ + if RC.cseStatus in [ CSEStatus.SHUTTINGDOWN, CSEStatus.STOPPED ]: + return + + # indicating the shutting down status. When running in another environment the + # atexit-handler might not be called. Therefore, we need to set it here + if RC.cseStatus != CSEStatus.SHUTTINGDOWNRESTART: # only set this if we are not restarting + RC.cseStatus = CSEStatus.SHUTTINGDOWN + if console: + console.stop() # This will end the main run loop. + + if mecClient: + mecClient.deregisterFromMec() + + if runsInIPython(): + L.console('CSE shut down', nlb = True) + + +@atexit.register +def _shutdown() -> None: + """ Shutdown the CSE, e.g. when receiving a keyboard interrupt or at the end of the programm run. + """ + if RC.cseStatus not in [CSEStatus.RUNNING, CSEStatus.SHUTTINGDOWNRESTART]: + return + L.console('CSE shutting down now', nlb=True) + + # The status STOPPINGRESTART is used to indicate that the CSE is shutting down to restart. + # This is a normal shutdown but in the end the CSE process will return with a special exit code + # to indicate that the CSE is restarting. This code is 82 (ASCII code for 'R'). + _cseStatus = RC.cseStatus + RC.cseStatus = CSEStatus.SHUTTINGDOWN + L.queueOff() + L.isInfo and L.log('CSE shutting down') + if event: # send shutdown event + event.cseShutdown() # type: ignore + + # shutdown the services + pluginManager and pluginManager.shutdown() + + textUI and textUI.shutdown() + console and console.shutdown() + time and time.shutdown() + location and location.shutdown() + semantic and semantic.shutdown() + remote and remote.shutdown() + coapServer and coapServer.shutdown() + webSocketServer and webSocketServer.shutdown() + mqttClient and mqttClient.shutdown() + httpServer and httpServer.shutdown() + script and script.shutdown() + announce and announce.shutdown() + timeSeries and timeSeries.shutdown() + groupResource and groupResource.shutdown() + notification and notification.shutdown() + request and request.shutdown() + dispatcher and dispatcher.shutdown() + security and security.shutdown() + validator and validator.shutdown() + registration and registration.shutdown() + statistics and statistics.shutdown() + event and event.shutdown() + storage and storage.shutdown() + + mecClient and mecClient.shutdown() + + L.isInfo and L.log('CSE shut down') + L.console('CSE shut down', nlb = True) + + L.finit() + RC.cseStatus = CSEStatus.STOPPED + + # If the CSE is stopping to restart, we exit with a special exit code + if _cseStatus == CSEStatus.SHUTTINGDOWNRESTART: + os._exit(82) + +def forceShutdown() -> None: + """ Force shutdown the CSE. + + This is different for different platforms. On Windows, we send a SIGINT to the process, + while on other platforms we raise a SIGINT signal. This is to ensure that the CSE can + shutdown gracefully, even if the main thread is blocked or busy. + + This function might not return, e.g. when running under Windows, where the process is killed. + """ + _platform = platform.system() + L.isDebug and L.logDebug(f'Forcing CSE shutdown (Platform: {_platform})') + + if textUI and textUI.tuiApp: # Shutdown the TextUI first + textUI.shutdown() + import time as _time + _time.sleep(1) # Give the TextUI a moment to shutdown + + # Platform specific shutdown + # On Windows, we send a SIGINT to the process, which will be caught by the main thread + match _platform: + case 'Windows': + _shutdown() + os.kill(os.getpid(), signal.SIGINT) + case _: + signal.raise_signal(signal.SIGINT) # raise SIGINT to shutdown the CSE + + +def resetCSE() -> None: + """ Reset the CSE: Clear databases and import the resources again. + """ + with _cseResetLock: + RC.cseStatus = CSEStatus.RESETTING + L.isWarn and L.logWarn('Resetting CSE started') + L.enableScreenLogging = Configuration.logging_enableScreenLogging # Set screen logging to the originally configured values + + L.setLogLevel(cast(LogLevel, Configuration.logging_level)) + L.queueOff() # Disable log queuing for restart + + httpServer.pause() + mqttClient.pause() + webSocketServer.shutdown() # WS Server needs to be shutdown to close connections + coapServer.pause() + + mecClient.pause() + + + storage.purge() + + # The following event is executed synchronously to give every component + # a chance to finish + event.cseReset() # type: ignore [attr-defined] + + # We only import policies, documentation and scripts during restart + # But we don't import the resource policies again. + if not importer.importPolicies() or not importer.importScripts(): + textUI and textUI.shutdown() + L.logErr('Error during import') + sys.exit() # what else can we do? + remote.restart() + + coapServer.unpause() + webSocketServer.run() # WS Server restart + mqttClient.unpause() + httpServer.unpause() + mecClient.unpause() + + # Enable log queuing again + L.queueOn() + + # Send restarted event + event.cseRestarted() # type: ignore [attr-defined] + + RC.cseStatus = CSEStatus.RUNNING + L.isWarn and L.logWarn('Resetting CSE finished') + + +def restartCSE() -> None: + """ Restart the CSE. This is a convenience function that calls the shutdown() function. + """ + if RC.cseStatus != CSEStatus.RUNNING: + L.logErr('CSE is not running, cannot restart') + return + L.isWarn and L._log(LogLevel.WARNING, 'Restarting CSE', immediate=True) + console.stop() + _shutdown() + RC.cseStatus = CSEStatus.SHUTTINGDOWNRESTART + + +def run() -> None: + """ Run the CSE. + + Raises: + TimeoutError: If the CSE does not start within the specified time. + """ + + L.logDebug(f'MEC enable: {Configuration.mec_enable}') + if Configuration.mec_enable: + mecClient.confirm_readyToMec() + mecClient.post_mec_service() + mecClient.registerToMec() + + if waitFor(C.cseStartupDelay * 3, lambda: RC.cseStatus==CSEStatus.RUNNING): + console.run() + else: + raise TimeoutError(L.logErr(f'CSE did not start within {C.cseStartupDelay * 3} seconds')) + + diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/Configuration.py b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/Configuration.py new file mode 100644 index 0000000000000000000000000000000000000000..ec1019fb2b27fdcf3b42454bb67cfc8a2ef063e5 --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/Configuration.py @@ -0,0 +1,1235 @@ +# +# Configuration.py +# +# (c) 2020 by Andreas Kraft +# License: BSD 3-Clause License. See the LICENSE file for further details. +# +# Managing CSE configurations +# +""" This module implements the configuration of the CSE. It reads the configuration file, performs checks, + and provides access to the configuration values. """ + + +from __future__ import annotations +from typing import Any, Dict, Tuple, Optional, cast, Set + +import configparser, argparse, os, os.path, pathlib +from copy import deepcopy +import time +from inspect import getmembers +from dotenv import load_dotenv, find_dotenv + +from rich.console import Console + + +from ..etc.Constants import Constants as C +from ..etc.Types import CSEType, ContentSerializationType, LogLevel, TreeMode, CSERegistrar +from ..helpers.NetworkTools import getIPAddress +from ..helpers.Zookeeper import Zookeeper +from ..helpers.ACMEConfiguration import ACMEConfiguration + + + + +# TODO: proper use of the baseDirectory configuration for other values + +# +# Deprecated secttions +# + +# Add deprecated sections here. Format: set of (oldSection, newSection) +_deprecatedSections:Set[Tuple[str, str]] = None +""" Deprecated sections. Mapping from old section name to new section name.""" + +class Configuration(object): + """ The static class Configuration holds all the configuration values of the CSE. It is initialized only once by calling the static + method init(). Access to configuration valus is done by calling Configuration.get() or by + accessing an attribute with the same name as the configuration key (with all "." replaced by "_"). + + Example: + :: + + print(Configuration.get('http.port')) + print(Configuration.http_port) + + + """ + + configParser:ACMEConfiguration = None + """ The ACMEConfiguration instance holding the configuration values. """ + + coap_enable:bool + """ Enable or disable the CoAP server. """ + + coap_listenIF:str + """ The network interface to listen on for CoAP. """ + + coap_port:int + """ The port to listen on for CoAP. """ + + coap_address:str + """ The address to listen on for CoAP. """ + + coap_timeout:float + """ The timeout for CoAP requests. """ + + coap_clientConnectionCacheSize:int + """ The size of the client connection cache. """ + + + coap_security_caCertificateFile:str + """ The CA certificate file for CoAP. """ + + coap_security_caPrivateKeyFile:str + """ The CA private key file for CoAP. """ + + coap_security_dtlsVersion:str + """ The DTLS version for CoAP. """ + + coap_security_useDTLS:bool + """ Enable or disable DTLS for CoAP. """ + + coap_security_verifyCertificate:bool + """ Enable or disable certificate verification for CoAP. """ + + + console_confirmQuit:bool + """ Confirm quitting the console. """ + + console_headless:bool + """ Run the CSE in headless mode. """ + + console_hideResources:list[str] + """ Resources to hide in the console. """ + + console_refreshInterval:float + """ The refresh interval for the console. """ + + console_theme:str + """ The theme for the console. """ + + console_treeIncludeVirtualResource:bool + """ Include virtual resources in the console tree. """ + + console_treeMode:str|TreeMode + """ The tree mode for the console. """ + + + cse_asyncSubscriptionNotifications:bool + """ Enable or disable asynchronous subscription notifications. """ + + cse_checkExpirationsInterval:int + """ The interval to check for resource expirations. """ + + cse_cseID:str + """ The CSE-ID of the CSE. """ + + cse_defaultSerialization:str|ContentSerializationType + """ The default serialization for the CSE. """ + + cse_enableRemoteCSE:bool + """ Enable or disable remote CSEs. """ + + cse_enableResourceExpiration:bool + """ Enable or disable resource expiration. """ + + cse_enableSubscriptionVerificationRequests:bool + """ Enable or disable subscription verification requests. """ + + cse_flexBlockingPreference:str + """ The flex blocking preference for the CSE. """ + + cse_maxExpirationDelta:int + """ The maximum expiration delta for resources. """ + + cse_originator:str + """ The originator for the CSE. """ + + cse_poa:list[str] + """ The Points of Access for the CSE. """ + + cse_releaseVersion:str + """ The release version of the CSE. """ + + cse_requestExpirationDelta:float + """ The request expiration delta for the CSE. """ + + cse_resourcesPath:str + """ The path to the resources. """ + + cse_resourceID:str + """ The resource ID of the CSE. """ + + cse_resourceName:str + """ The resource name of the CSE. """ + + cse_sendToFromInResponses:bool + """ Send the To and From in responses. """ + + cse_sortDiscoveredResources:bool + """ Sort discovered resources. """ + + cse_supportedReleaseVersions:list[str] + """ The supported release versions of the CSE. """ + + cse_serviceProviderID:str + """ The service provider ID of the CSE. """ + + cse_type:str|CSEType + """ The type of the CSE. """ + + cse_idLength:int + """ The length of the generated resource IDs. """ + + cse_announcements_allowAnnouncementsToHostingCSE:bool + """ Allow announcements to the hosting CSE. """ + + cse_announcements_delayAfterRegistration:float + """ The delay after registration for announcements. """ + + + cse_operation_jobs_balanceLatency:int + """ The latency for balancing jobs. """ + + cse_operation_jobs_balanceReduceFactor:float + """ The reduce factor for balancing jobs. """ + + cse_operation_jobs_balanceTarget:float + """ The target for balancing jobs. """ + + + cse_operation_requests_enable:bool + """ Enable or disable operation requests. """ + + cse_operation_requests_size:int + """ The size of the operation requests. """ + + + cse_operation_plugins_disabledPlugins:list[str] + """ A list of disabled plugins. """ + + cse_operation_plugins_replace:bool + """ Replace existing plugins with the same name. """ + + + cse_registrars:dict[str, CSERegistrar] = {} + """ A dictionary of CSE or service provider CSEs registrars. The keys are the CSE IDs, the values are dictionaries with the registrar information. """ + + + cse_registration_allowedAEOriginators:list[str] + """ Allowed AE originators for registration. """ + + cse_registration_allowedCSROriginators:list[str] + """ Allowed CSR originators for registration. """ + + cse_registration_checkLiveliness:bool + """ Check liveliness for registration. """ + + cse_registration_checkInterval:int + """ Time interval to check liveliness of registration(s). """ + + cse_registration_unregisterWhenStopping:bool + """ Unregister the CSR resource when stopping the CSE. """ + + cse_security_secret:str + """ The main secret key for the CSE. """ + + cse_security_enableACPChecks:bool + """ Enable or disable ACP checks. """ + + cse_security_fullAccessAdmin:bool + """ Full access for admin. """ + + + database_type:str + """ The type of the database. """ + + database_resetOnStartup:bool + """ Reset the database on startup. """ + + database_backupPath:str + """ The path for the database backup. """ + + + database_tinydb_path:str + """ The path to the TinyDB database. """ + + database_tinydb_cacheSize:int + """ The cache size for the TinyDB database. """ + + database_tinydb_writeDelay:int + """ The write delay for the TinyDB database. """ + + + database_postgresql_host:str + """ The host of the PostgreSQL database. """ + + database_postgresql_port:int + """ The port of the PostgreSQL database. """ + + database_postgresql_role:str + """ The role of the PostgreSQL database. """ + + database_postgresql_password:str + """ The password of the PostgreSQL database. """ + + database_postgresql_database:str + """ The database of the PostgreSQL database. """ + + database_postgresql_schema:str + """ The schema of the PostgreSQL database. """ + + fullAddress:str + """ The full address of the MN-CSE. """ + + http_address:str + """ The address to listen on for HTTP the http server. """ + + http_allowPatchForDelete:bool + """ Allow PATCH for DELETE operations. """ + + http_enableStructureEndpoint:bool + """ Enable the structure endpoint. """ + + http_enableUpperTesterEndpoint:bool + """ Enable the upper tester endpoint. """ + + http_enableManagementEndpoint:bool + """ Enable the management endpoint. """ + + http_listenIF:str + """ The network interface to listen on for HTTP. """ + + http_port:int + """ The port to listen on for HTTP. """ + + http_root:str + """ The root of the HTTP path. """ + + http_externalRoot:str + """ The non-local root path of the HTTP path. This is used when the CSE is accessed from non-local addresses, e.g. in a Kubernetes cluster. """ + + http_timeout:float + """ The timeout for HTTP requests. """ + + + http_cors_enable:bool + """ Enable or disable CORS. """ + + http_cors_resources:list[str] + """ The resources for CORS. """ + + + http_security_caCertificateFile:str + """ The CA certificate file for HTTP. """ + + http_security_caPrivateKeyFile:str + """ The CA private key file for HTTP. """ + + http_security_tlsVersion:str + """ The TLS version for HTTP. """ + + http_security_useTLS:bool + """ Enable or disable TLS for HTTP. """ + + http_security_verifyCertificate:bool + """ Enable or disable certificate verification for HTTP. """ + + http_security_enableBasicAuth:bool + """ Enable or disable basic authentication for HTTP. """ + + http_security_enableTokenAuth:bool + """ Enable or disable token authentication for HTTP. """ + + http_security_basicAuthFile:str + """ The file for basic authentication for HTTP. """ + + http_security_tokenAuthFile:str + """ The file for token authentication for HTTP. """ + + + http_wsgi_enable:bool + """ Enable or disable the WSGI server. """ + + http_wsgi_connectionLimit:int + """ The connection limit for the WSGI server. """ + + http_wsgi_threadPoolSize:int + """ The thread pool size for the WSGI server. """ + + + logging_count:int + """ The number of log entries. """ + + logging_enableBindingsLogging:bool + """ Enable or disable bindings logging. """ + + logging_enableFileLogging:bool + """ Enable or disable file logging. """ + + logging_enableScreenLogging:bool + """ Enable or disable screen logging. """ + + logging_filter:list + """ The filter for logging. """ + + logging_level:str|LogLevel + """ The log level. """ + + logging_maxLogMessageLength:int + """ The maximum log message length. """ + + logging_path:str + """ The path for logging. """ + + logging_queueSize:int + """ The queue size for logging. """ + + logging_size:int + """ The size of the log. """ + + logging_stackTraceOnError:bool + """ Enable or disable stack trace on error. """ + + logging_enableUTCTimezone:bool + """ Enable or disable UTC timezone. """ + + mec_app_instance_id:str + """ MEC application instance id. """ + + mqtt_address:str + """ The address to listen on for the MQTT server. """ + + mqtt_enable:bool + """ Enable or disable the MQTT server. """ + + mqtt_keepalive:int + """ The keepalive for MQTT. """ + + mqtt_listenIF:str + """ The network interface to listen on for MQTT. """ + + mqtt_port:int + """ The port to listen on for MQTT. """ + + mqtt_timeout:float + """ The timeout for MQTT requests. """ + + mqtt_topicPrefix:str + """ The topic prefix for MQTT. """ + + mec_enable:bool + mec_host:str + mec_port:int + mec_protocol:str + mec_platform:str + mec_sandbox_id:str + + mqtt_security_allowedCredentialIDs:list[str] + """ The allowed credential IDs for MQTT. """ + + mqtt_security_caCertificateFile:str + """ The CA certificate file for MQTT. """ + + mqtt_security_password:str + """ The password for MQTT. """ + + mqtt_security_username:str + """ The username for MQTT. """ + + mqtt_security_useTLS:bool + """ Enable or disable TLS for MQTT. """ + + mqtt_security_verifyCertificate:bool + """ Enable or disable certificate verification for MQTT. """ + + mqtt_websocket_enable:bool + """ Enable or disable the MQTT over WebSocket. """ + + mqtt_websocket_port:int + """ The WebSocket port for MQTT. """ + + mqtt_websocket_path:str + """ The WebSocket path for MQTT. """ + + resource_acp_selfPermission:int + """ The self permission for ACP. """ + + + resource_actr_ecpContinuous:int + """ The continuous for ACTR. """ + + resource_actr_ecpPeriodic:int + """ The periodic for ACTR. """ + + + resource_cnt_enableLimits:bool + """ Enable or disable limits for CNT. """ + + resource_cnt_mni:int + """ The MNI for CNT. """ + + resource_cnt_mbs:int + """ The MBS for CNT. """ + + resource_cnt_mia:int + """ The MIA for CNT. """ + + + resource_fcnt_enableLimits:bool + """ Enable or disable limits for FCNT. """ + + resource_fcnt_mni:int + """ The MNI for FCNT. """ + + resource_fcnt_mbs:int + """ The MBS for FCNT. """ + + resource_fcnt_mia:int + """ The MIA for FCNT. """ + + + resource_grp_resultExpirationTime:int + """ The result expiration time for GRP. """ + + resource_lcp_mni:int + """ The MNI for LCP. """ + + resource_lcp_mbs:int + """ The MBS for LCP. """ + + + resource_req_et:int + """ The expiration time for REQ. """ + + + resource_sub_batchNotifyDuration:int + """ The batch notify duration for SUB. """ + + + resource_ts_enableLimits:bool + """ Enable or disable limits for TS. """ + + resource_ts_mbs:int + """ The MBS for TS. """ + + resource_ts_mdn:int + """ The MDN for TS. """ + + resource_ts_mni:int + """ The MNI for TS. """ + + resource_ts_mia:int + """ The MIA for TS. """ + + + resource_tsb_bcni:str + """ The BCNI for TSB. """ + + resource_tsb_bcnt:float + """ The BCNT for TSB. """ + + + scripting_fileMonitoringInterval:float + """ The file monitoring interval for scripting. """ + + scripting_maxRuntime:float + """ The maximum runtime for scripting. """ + + scripting_scriptDirectories:list[str] + """ The script directories for scripting. """ + + scripting_verbose:bool + """ Enable or disable verbose mode for scripting. """ + + + cse_statistics_enable:bool + """ Enable or disable statistics. """ + + cse_statistics_writeInterval:int + """ The write interval for statistics. """ + + + textui_refreshInterval:float + """ The refresh interval for the text UI. """ + + textui_startWithTUI:bool + """ Start with the text UI. """ + + textui_theme:str + """ The theme for the text UI. """ + + textui_maxRequestSize:int + """ The maximum request size for the text UI. """ + + textui_notificationTimeout:float + """ The notification timeout for the text UI. """ + + textui_enableTextEditorSyntaxHighlighting:bool + """ Enable or disable text editor syntax highlighting for the text UI. """ + + + webui_root:str + """ The root path for the web UI. """ + + + websocket_enable:bool + """ Enable or disable the WebSocket server. """ + + + websocket_address:str + """ The address to listen on for the WebSocket server. """ + + websocket_listenIF:str + """ The network interface to listen on for WebSocket. """ + + websocket_loglevel:int|str + """ The log level for WebSocket. """ + + websocket_port:int + """ The port to listen on for WebSocket. """ + + websocket_timeout:float + """ The timeout for WebSocket requests. """ + + + websocket_security_caCertificateFile:str + """ The CA certificate file for WebSocket. """ + + websocket_security_caPrivateKeyFile:str + """ The CA private key file for WebSocket. """ + + websocket_security_tlsVersion:str + """ The TLS version for WebSocket. """ + + websocket_security_useTLS:bool + """ Enable or disable TLS for WebSocket. """ + + websocket_security_verifyCertificate:bool + """ Enable or disable certificate verification for WebSocket. """ + + websocket_security_enableBasicAuth:bool + """ Enable or disable basic authentication for WebSocket. """ + + websocket_security_enableTokenAuth:bool + """ Enable or disable token authentication for WebSocket. """ + + websocket_security_basicAuthFile:str + """ The file for basic authentication for WebSocket. """ + + websocket_security_tokenAuthFile:str + """ The file for token authentication for WebSocket. """ + + + moduleDirectory:pathlib.Path = None + """ The base directory of the ACME module. """ + baseDirectory:pathlib.Path = None + """ The base directory of the ACME module. """ + initDirectory:pathlib.Path = None + """ The init directory of the ACME module. """ + configfile:str = None + """ The configuration file. """ + + + _configurationDocs: Dict[str, str] = {} + """ The configuration values documentation as a dictionary. """ + + _defaultConfigFilePath:pathlib.Path = None + """ The default init file. """ + + _defaultConfigFile:str = None + """ The default configuration file. """ + + _args_configfile:str = None + """ The configuration file passed as argument. This overrides the respective value in the configuration file. """ + _args_loglevel:str = None + """ The log level passed as argument. This overrides the respective value in the configuration file. """ + _args_DBReset:bool = None + """ The reset DB flag passed as argument. This overrides the respective value in the configuration file. """ + _args_DBStorageMode:str = None + """ The DB storage mode passed as argument. This overrides the respective value in the configuration file. """ + _args_DBDataDirectory:str = None + """ The DB data directory passed as argument. This overrides the respective value in the configuration file. """ + _args_headless:bool = None + """ The headless flag passed as argument. This overrides the respective value in the configuration file. """ + _args_httpAddress:str = None + """ The http address passed as argument. This overrides the respective value in the configuration file. """ + _args_httpPort:int = None + """ The http port passed as argument. This overrides the respective value in the configuration file. """ + _args_initDirectory:str = None + """ The import directory passed as argument. This overrides the respective value in the configuration file. """ + _args_lightScheme:str = None + """ The light scheme flag passed as argument. This overrides the respective value in the configuration file. """ + _args_listenIF:str = None + """ The network interface passed as argument. This overrides the respective value in the configuration file. """ + _args_coapEnabled:bool = None + """ The coap enabled flag passed as argument. This overrides the respective value in the configuration file. """ + _args_mqttEnabled:bool = None + """ The mqtt enabled flag passed as argument. This overrides the respective value in the configuration file. """ + _args_wsEnabled:bool = None + """ The WebSocket enabled flag passed as argument. This overrides the respective value in the configuration file. """ + _args_remoteCSEEnabled:bool = None + """ The remote CSE enabled flag passed as argument. This overrides the respective value in the configuration file. """ + _args_runAsHttps:bool = None + """ The https flag passed as argument. This overrides the respective value in the configuration file. """ + _args_runAsHttpWsgi:bool = None + """ The http WSGI flag passed as argument. This overrides the respective value in the configuration file. """ + _args_baseDirectory:str = None + """ The runtime data directory passed as argument. This overrides the default (the CWD). """ + _args_statisticsEnabled:bool = None + """ The statistics enabled flag passed as argument. This overrides the respective value in the configuration file. """ + _args_textUI:bool = None + """ The text UI flag passed as argument. This overrides the respective value in the configuration file. """ + _args_zkHost:str = None + """ The Zookeeper host passed as argument. **There is no equivalent in the configuration file.** """ + _args_zkPort:int = None + """ The Zookeeper port passed as argument. **There is no equivalent in the configuration file.** """ + _args_zkRoot:str = None + """ The Zookeeper root node passed as argument. **There is no equivalent in the configuration file.** """ + + _args_mec_enable:bool = None + _args_mec_host:str = None + _args_mec_port:int = None + _args_mec_protocol:str = None + _args_mec_platform:str = None + _args_mec_sandbox_id:str = None + _cse_external_ip:str = None + _cse_external_port:str = None + + # Internal print function that takes the headless setting into account + @staticmethod + def _print(msg:str, markup:Optional[bool]=True) -> None: + """ Print a message to the console. If the CSE is running in headless mode, then the message is not printed. + + Args: + msg: The message to print. + markup: If True, then the message is printed with markup. If False, then the message is printed without markup. + """ + if not Configuration._args_headless: + Console().print(msg, markup=markup) # Print error message to console + + + @staticmethod + def _warning(msg:str) -> None: + """ Print a warning message to the console. + + Args: + msg: The warning message to print. + """ + Configuration._print(f'[orange3][b u]Configuration Warning[/b u]\n{msg}[/orange3]\n') + + + @staticmethod + def _error(msg:str) -> None: + """ Print an error message to the console. + + Args: + msg: The error message to print. + """ + Configuration._print(f'[red][b u]Configuration Error[/b u]\n{msg}[/red]\n') + + + + @staticmethod + def initDirectories() -> bool: + """ Initialize the directories for the configuration. This method must be called before accessing any configuration value. + + Returns: + True on success, False otherwise. + """ + + # The path to the ACME module directory + Configuration.moduleDirectory = pathlib.Path(os.path.abspath(os.path.dirname(__file__))).parent + + # Test that the config filename is just a filename without a path. If it is then throw an error + if Configuration._args_configfile and os.path.dirname(Configuration._args_configfile): + Configuration._error(f'Configuration file must be a filename without a path: {Configuration._args_configfile}') + return False + + # Find out the path to the init directory + Configuration.initDirectory = Configuration.moduleDirectory / 'init' + if Configuration._args_initDirectory: # Use the init directory if given as argument + Configuration.initDirectory = pathlib.Path(Configuration._args_initDirectory) + + # Get the path to the runtime data directory + Configuration.baseDirectory = pathlib.Path(os.getcwd()) + if Configuration._args_baseDirectory: # Use the runtime data directory if given as argument + Configuration.baseDirectory = pathlib.Path(Configuration._args_baseDirectory) + + # Set the default ini file and check if it exists and is readable + Configuration._defaultConfigFilePath = Configuration.initDirectory / C.defaultConfigFile + Configuration._defaultConfigFile = str(Configuration._defaultConfigFilePath) + if not os.access(Configuration._defaultConfigFile, os.R_OK): + Configuration._error(f'Default configuration file missing or not readable: {Configuration._defaultConfigFile}') + return False + + return True + + + @staticmethod + def init(args:Optional[argparse.Namespace] = None) -> bool: + """ Initialize and read the configuration. This method must be called before accessing any configuration value. + + Args: + args: Optional arguments. If not given, then the command line arguments are used. + + Returns: + True on success, False otherwise. + """ + + def runOnboarding(withZookeeper: bool=False) -> bool: + """ Run the onboarding process to create a user configuration file if it does not exist. + + Args: + withZookeeper: If True, then the onboarding process will use Zookeeper for configuration management. If False, then the onboarding process will create a local configuration file. + Returns: + True if the onboarding was successful, False otherwise. + + Raises: + Exception: If an error occurs during the onboarding process. + """ + try: + if Configuration._args_headless: + Console().print(f'[red]Configuration file: {Configuration._args_configfile} is missing and cannot be created in headless mode.\n') + return False + + # load onboarding module and create user config file. + # After that, remove the module from the modules list, because it is not needed anymore + from ..runtime import Onboarding + result, _configFile, _baseDirectory = Onboarding.buildUserConfigFile(Configuration._args_configfile if not withZookeeper else None, + (Configuration._args_zkHost, Configuration._args_zkPort, Configuration._args_zkRoot)) + import sys + del sys.modules[Onboarding.__name__] # Remove the module again to save some memory + del Onboarding + + if not result: + return False + if _configFile: + Configuration._args_configfile = str(pathlib.Path(_configFile)) + if _baseDirectory: + Configuration.baseDirectory = pathlib.Path(_baseDirectory) + except Exception as e: + Console().print(e) + raise e + return True + + # resolve the args and set them as attributes + Configuration._args_configfile = args.configfile if args and 'configfile' in args and args.configfile else C.defaultUserConfigFile + Configuration._args_baseDirectory = args.rtDirectory if args and 'rtDirectory' in args else None # baseDirectory + Configuration._args_loglevel = args.loglevel if args and 'loglevel' in args else None + Configuration._args_DBReset = args.dbreset if args and 'dbreset' in args else False + Configuration._args_DBStorageMode = args.dbstoragemode if args and 'dbstoragemode' in args else None + Configuration._args_DBDataDirectory = args.dbdirectory if args and 'dbdirectory' in args else None + Configuration._args_headless = args.headless if args and 'headless' in args else False + Configuration._args_httpAddress = args.httpaddress if args and 'httpaddress' in args else None + Configuration._args_httpPort = args.httpport if args and 'httpport' in args else None + Configuration._args_initDirectory = args.initdirectory if args and 'initdirectory' in args else None + Configuration._args_lightScheme = args.lightScheme if args and 'lightScheme' in args else None + Configuration._args_listenIF = args.listenif if args and 'listenif' in args else None + Configuration._args_mqttEnabled = args.mqttenabled if args and 'mqttenabled' in args else None + Configuration._args_remoteCSEEnabled = args.remotecseenabled if args and 'remotecseenabled' in args else None + Configuration._args_runAsHttps = args.https if args and 'https' in args else None + Configuration._args_runAsHttpWsgi = args.httpWsgi if args and 'httpWsgi' in args else None + Configuration._args_statisticsEnabled = args.statisticsenabled if args and 'statisticsenabled' in args else None + Configuration._args_textUI = args.textui if args and 'textui' in args else None + Configuration._args_coapEnabled = args.coapenabled if args and 'coapenabled' in args else None + Configuration._args_wsEnabled = args.wsenabled if args and 'wsenabled' in args else None + Configuration._args_zkHost = args.zkHost if args and 'zkHost' in args else None + Configuration._args_zkPort = args.zkPort if args and 'zkPort' in args else None + Configuration._args_zkRoot = args.zkRoot if args and 'zkRoot' in args else None + Configuration._args_mec_enable = args.mec_enable if args and 'mec_enable' in args else None + Configuration._args_mec_host = args.mec_host if args and 'mec_host' in args else None + Configuration._args_mec_port = args.mec_port if args and 'mec_port' in args else None + Configuration._args_mec_protocol = args.mec_protocol if args and 'mec_protocol' in args else None + Configuration._args_mec_platform = args.mec_platform if args and 'mec_platform' in args else None + Configuration._args_mec_sandbox_id = args.mec_sandbox_id if args and 'mec_sandbox_id' in args else None + + + if not Configuration.initDirectories(): + return False + + # The list of configuration files to read (significantly, the default config file is always the first one) + # The list of configuration strings contain optional extra strings in ini format. They are interpolated + # after the configuration files are read. + configurationFiles = [Configuration._defaultConfigFile] + configurationStrings:list[str] = [] + + # Check if there are no arguments given for the Zookeeper host and root node. Then use the + # normal file-based configuration + if not (Configuration._args_zkHost and Configuration._args_zkRoot): + + # Check and re-set the configuration file's path if the runtime data directory is given AND + # the configuration file is not given as argument + if Configuration._args_baseDirectory and not args.configfile: + Configuration._args_configfile = f'{Configuration._args_baseDirectory}/{C.defaultUserConfigFile}' + + # Adapt configuration file path to the runtime data directory + Configuration._args_configfile = f'{Configuration.baseDirectory}{os.sep}{os.path.basename(Configuration._args_configfile)}' + + # Create user config file if doesn't exist + if not os.path.exists(Configuration._args_configfile): + if not runOnboarding(): + return False + Configuration.configfile = Configuration._args_configfile + configurationFiles.append(Configuration.configfile) + + # Read the configuration from a Zookeeper server + else: + try: + zk = Zookeeper(host=Configuration._args_zkHost, + port=Configuration._args_zkPort, + rootNode=Configuration._args_zkRoot, + logger=lambda x: Configuration._print(x), + caseSensitive=False, # switch off case sensitivity for ini files + ).connect(createRoot=False) + if not zk.exists(): + if not runOnboarding(withZookeeper=True): + return False + # try again to connect to read the configuration + if not zk.exists(): + raise Exception(f'Root node "{Configuration._args_zkRoot}" does not exist.') + configurationStrings.append(zk.retrieveIniConfig()) + except Exception as e: + import traceback + traceback.print_exc() + Configuration._error(f'Error connecting to Zookeeper server: {e}') + return False + finally: + zk.disconnect() + + # Read and parse the configuration file + Configuration.configParser = ACMEConfiguration() + + # Construct the default values that are used for interpolation + _defaults = { 'basic.config': { + 'baseDirectory' : Configuration.baseDirectory, # points to the currenr working directory + 'moduleDirectory' : Configuration.moduleDirectory, # points to the acme module's directory + 'initDirectory' : Configuration.initDirectory, # points to the acme/init directory + 'hostIPAddress' : getIPAddress(), # provide the IP address of the host + 'networkInterface' : '0.0.0.0', # The network interface to listen on for HTTP, CoAP, MQTT and WebSocket. + + 'serviceProviderID' : '//acme.example.com', # The service provider ID of the CSE. + 'registrarCseHost' : getIPAddress(), # The IP address of the registrar CSE + 'registrarCsePort' : 8080, # The TCP port of the registrar CSE + 'registrarCseID' : '', # The CSE-ID of the registrar CSE + 'registrarCseName' : '', # The resource name of the registrar CSE's CSEBase + + 'logLevel' : 'debug', # The main secret key for the CSE. + 'consoleTheme' : 'dark', # The theme for the console. + 'secret' : os.getenv('ACME_SECURITY_SECRET', 'acme'), # The main secret key for the CSE. + } + } + # Load environment variables from .env file from the base directory, if it exists + load_dotenv(dotenv_path=f'{Configuration.baseDirectory}{os.path.sep}.env') + + # Add environment variables to the defaults + _defaults.update({ 'DEFAULT': {k: v.replace('$', '$$') for k,v in os.environ.items()} }) + + # Check wether none of the environment variables has the same name as any of the default values in "basic.config" + for k in _defaults['basic.config'].keys(): + if k in os.environ: + Configuration._warning(fr'The environment variable "{k}" conflicts with an option with the same name in the section \[[i]basic.config[/i]].\nPlease consider renaming the environment variable otherwise it cannot be used for interpolation in that section.') + + # Add (empty) default for supported environment variables to the defaults dictionary for the interpolation during reading the configuration file + _envVariables = { e: os.getenv(e, '') if e not in _defaults else _defaults[e] + for e in ( + 'ACME_MQTT_SECURITY_PASSWORD', + 'ACME_MQTT_SECURITY_USERNAME', + 'ACME_DATABASE_POSTGRESQL_PASSWORD', + 'ACME_CSE_REGISTRAR_SECURITY_HTTPUSERNAME', + 'ACME_CSE_REGISTRAR_SECURITY_HTTPPASSWORD', + 'ACME_CSE_REGISTRAR_SECURITY_HTTPBEARERTOKEN', + 'ACME_CSE_REGISTRAR_SECURITY_WSUSERNAME', + 'ACME_CSE_REGISTRAR_SECURITY_WSPASSWORD', + 'ACME_CSE_REGISTRAR_SECURITY_WSBEARERTOKEN', + 'ACME_CSE_REGISTRAR_SECURITY_SELFHTTPUSERNAME', + 'ACME_CSE_REGISTRAR_SECURITY_SELFHTTPPASSWORD', + 'ACME_CSE_REGISTRAR_SECURITY_SELFWSUSERNAME', + 'ACME_CSE_REGISTRAR_SECURITY_SELFWSPASSWORD', + ) + } + _defaults['DEFAULT'].update(_envVariables) + + # Set the defaults + Configuration.configParser.read_dict(_defaults) + + try: + # Read the configuration files + # if len(config.read( [Configuration._defaultConfigFile, Configuration.configfile])) == 0 and Configuration._args_configfile != C.defaultUserConfigFile: # Allow + if len(Configuration.configParser.read(configurationFiles)) == 0 and Configuration._args_configfile != C.defaultUserConfigFile: # Allow + Configuration._error(f'Configuration file missing or not readable: {Configuration._args_configfile}') + return False + + # Read the extra configuration strings (e.g. from Zookeeper) + for cs in configurationStrings: + Configuration.configParser.read_string(cs) + + except configparser.Error as e: + Configuration._error(f'Error in configuration file\n{str(e)}') + return False + + # Look for deprecated and renamed sections and print an error message + if _deprecatedSections: + for o, n in _deprecatedSections: + if Configuration.configParser.has_section(o): + Configuration._error(fr'Found old section name in configuration file. Please rename "\[{o}]" to "\[{n}]".') + return False + + # Retrieve configuration values + try: + + # Call the configuration handler for each module + # The "readConfiguration" methods are responsible for reading the configuration values from the configuration file + # and to set the respective attributes in the Configuration class. + # Validations are done later below. + for m in _moduleConfigs: + m.readConfiguration(Configuration.configParser, Configuration) # type:ignore [arg-type] + + except configparser.InterpolationMissingOptionError as e: + Configuration._error(fr'Error in configuration file: {Configuration.configfile}\n{str(e)}\n\nPlease provide this configuration option in the section \[[i]basic.config[/i]], or set an environment variable with that name.\n') + return False + + except Exception as e: # about when findings errors in configuration + Configuration._error(f'Error in configuration file: {Configuration.configfile}\n{str(e)}') + return False + + # Validate the configuration for each module + for m in _moduleConfigs: + try: + m.validateConfiguration(Configuration, True) # type:ignore [arg-type] + except ConfigurationError as e: + Configuration._error(f'{str(e)}') + return False + + return True + + + @staticmethod + def print() -> str: + """ Prints the current configuration to the console. + + Returns: + A string with the current configuration. + """ + result = 'Configuration:\n' # Magic string used e.g. in tests, don't remove + for (k, v) in Configuration.all().items(): + result += f' {k} = {v}\n' + return result + + + @staticmethod + def all() -> Dict[str, Any]: + """ Returns the complete configuration as a dictionary. + + Returns: + A dictionary with the complete configuration. + """ + def isprop(v:Any) -> bool: + return not callable(v) + + attributeNames = [ k for k,v in getmembers(Configuration, isprop) if not k.startswith('_') ] + result = {} + for k in attributeNames: + attr = getattr(Configuration, k) + match attr: + case pathlib.Path(): + # Convert pathlib.Path to string + attr = str(attr) + case dict(): + # Don't change the original dict, so make a copy + attr = deepcopy(attr) + # Convert dict elements to instances dict, if necessary + for k2,v2 in attr.items(): + if isinstance(v2, CSERegistrar): + # Convert the CSERegistrar to a dict + attr[k2] = v2.toDict() + + # Replace underscores with dots in the key names + result[k.replace('_', '.')] = attr + return deepcopy(result) # make sure that the result is a deep copy of the configuration + + + @staticmethod + def get(key: str) -> Any: + """ Retrieve a configuration value or None if no configuration could be found for a key. + The key is case-insensitive and dots are replaced by underscores. + + Example: + The following example retrieves the value of the configuration key 'http.port': + + :: + + print(Configuration.get('http.port')) + + Args: + key: The configuration key to retrieve. + + Returns: + The configuration value or None if no configuration could be found for a key. + """ + if Configuration.has(key): + return cast(Any, getattr(Configuration, key.replace('.', '_'))) + return None + + + @staticmethod + def addDoc(key: str, markdown:str) -> None: + """ Adds a documentation for a configuration key. + + Args: + key: The configuration key to add the documentation for. + markdown: The documentation in markdown format. + """ + if key: + Configuration._configurationDocs[key] = markdown + + + @staticmethod + def getDoc(key:str) -> Optional[str]: + """ Retrieves the documentation for a configuration key. + + Args: + key: The configuration key to retrieve the documentation for. + + Returns: + The documentation in markdown format or None if no documentation could be found for the key. + """ + return Configuration._configurationDocs.get(key) + + + + # TODO change to exception + @staticmethod + def update(key:str, value:Any) -> Optional[str]: + """ Update a configuration value and inform other components via an event. + + Args: + key: The configuration key to update. + value: The new value for the configuration key. + + Returns: + None if no error occurs, or a string with an error message, what has gone wrong while validating + """ + if not Configuration.has(key): + return f'Unknown key: {key}' + if value is not None: # ignore invalid values + original = Configuration.get(key) + setattr(Configuration, key.replace('.', '_'), value) + + # TODO This worked before when there was only one validation function. + # Now that we have multiple validation functions, we need to call them all, or + # we need to store the proper validation function in the configuration for each key. + # if not (r := Configuration.validate())[0]: + # Configuration._configuration[key] = original + # return r[1].replace(r'\[', '[') # unescape "\[" in error messages + + from . import CSE + CSE.event.configUpdate(key, value) # type:ignore [attr-defined] + else: + return f'Invalid value for key: {key}' + return None + + + @staticmethod + def has(key:str) -> bool: + """ Check whether a configuration setting exsists. + + Args: + key: The configuration key to check. + + Returns: + True if the configuration key exists, False otherwise. + """ + return hasattr(Configuration, key.replace('.', '_')) + + +class ConfigurationError(Exception): + pass + + +############################################################################# +# +# Instantiating Configuration modules +# +# These modules are responsible for reading and validating their own configuration +# +# This happens at the end of the module, because the Configuration class must be +# initialized before the modules can be initialized. + +# Import all configuration modules + +from ..runtime.configurations.ACPResourceConfiguration import ACPResourceConfiguration +from ..runtime.configurations.ACTRResourceConfiguration import ACTRResourceConfiguration +from ..runtime.configurations.AnnouncementServiceConfiguration import AnnouncementServiceConfiguration +from ..runtime.configurations.CNTResourceConfiguration import CNTResourceConfiguration +from ..runtime.configurations.CoAPServerConfiguration import CoAPServerConfiguration +from ..runtime.configurations.ConsoleConfiguration import ConsoleConfiguration +from ..runtime.configurations.CSEConfiguration import CSEConfiguration +from ..runtime.configurations.FCNTResourceConfiguration import FCNTResourceConfiguration +from ..runtime.configurations.GroupServiceConfiguration import GroupServiceConfiguration +from ..runtime.configurations.HTTPServerConfiguration import HTTPServerConfiguration +from ..runtime.configurations.LCPResourceConfiguration import LCPResourceConfiguration +from ..runtime.configurations.LoggingConfiguration import LoggingConfiguration +from ..runtime.configurations.ModuleConfiguration import ModuleConfiguration +from ..runtime.configurations.MQTTConfiguration import MQTTConfiguration +from ..runtime.configurations.PostgreSQLBindingConfiguration import PostgreSQLBindingConfiguration +from ..runtime.configurations.RegistrationServiceConfiguration import RegistrationServiceConfiguration +from ..runtime.configurations.RemoteCSEServiceConfiguration import RemoteCSEServiceConfiguration +from ..runtime.configurations.REQResourceConfiguration import REQResourceConfiguration +from ..runtime.configurations.ScriptingConfiguration import ScriptingConfiguration +from ..runtime.configurations.SecurityServiceConfiguration import SecurityServiceConfiguration +from ..runtime.configurations.StatisticsConfiguration import StatisticsConfiguration +from ..runtime.configurations.StorageConfiguration import StorageConfiguration +from ..runtime.configurations.SUBResourceConfiguration import SUBResourceConfiguration +from ..runtime.configurations.TextUIConfiguration import TextUIConfiguration +from ..runtime.configurations.TinyDBBindingConfiguration import TinyDBBindingConfiguration +from ..runtime.configurations.TSBResourceConfiguration import TSBResourceConfiguration +from ..runtime.configurations.TSResourceConfiguration import TSResourceConfiguration +from ..runtime.configurations.WebSocketConfiguration import WebSocketConfiguration +from ..runtime.configurations.MECClientConfiguration import MECClientConfiguration + + +# Instantiate all configuration modules here, in a specfic order. + +_moduleConfigs:list[ModuleConfiguration] = [ + + # Runtime configurations + CSEConfiguration(), + TextUIConfiguration(), # must get its config before the Console ! + ConsoleConfiguration(), + LoggingConfiguration(), # must get its config after the Console ! + ScriptingConfiguration(), + StatisticsConfiguration(), + + # Service configurations + AnnouncementServiceConfiguration(), + GroupServiceConfiguration(), + RegistrationServiceConfiguration(), + RemoteCSEServiceConfiguration(), + SecurityServiceConfiguration(), + + # Storage configurations + StorageConfiguration(), + PostgreSQLBindingConfiguration(), + TinyDBBindingConfiguration(), + + # Binding configurations + CoAPServerConfiguration(), + HTTPServerConfiguration(), + MQTTConfiguration(), + WebSocketConfiguration(), + + # Resource configurations + ACPResourceConfiguration(), + ACTRResourceConfiguration(), + CNTResourceConfiguration(), + FCNTResourceConfiguration(), + LCPResourceConfiguration(), + REQResourceConfiguration(), + SUBResourceConfiguration(), + TSResourceConfiguration(), + TSBResourceConfiguration(), + + MECClientConfiguration(), + +] diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/configurations/MECClientConfiguration.py b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/configurations/MECClientConfiguration.py new file mode 100644 index 0000000000000000000000000000000000000000..c94fb6bbeaef92e8503f2000ffbea467b20baafe --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/acme/runtime/configurations/MECClientConfiguration.py @@ -0,0 +1,71 @@ +# +# MECServerMECClientConfigurationConfiguration.py +# +# (c) 2025 by Yann Garcia +# License: BSD 3-Clause License. See the LICENSE file for further details. +# +# MEC Client configurations +# + +from __future__ import annotations +from typing import Optional + +import configparser, os + +from ..Configuration import Configuration, ConfigurationError +from .ModuleConfiguration import ModuleConfiguration +from ...etc.Utils import normalizeURL +from ...helpers.NetworkTools import isValidPort, isValidateIpAddress, isValidateHostname + +class MECClientConfiguration(ModuleConfiguration): + + def readConfiguration(self, parser:configparser.ConfigParser, config:Configuration) -> None: + """ Read the configuration from the configuration file. + + Args: + parser: The configuration parser. + config: The configuration object. + """ + + config.mec_enable = parser.getboolean('etsi_mec', 'mec_enable', fallback = False) + config.mec_host = parser.get('etsi_mec', 'mec_host', fallback = 'www.try-mec.etsi.org') + config.mec_port = parser.getint('etsi_mec', 'mec_port', fallback = 443) + config.cse_external_ip = parser.get('etsi_mec', 'cse_external_ip', fallback = 'www.try-mec.etsi.org') + config.cse_external_port = parser.get('etsi_mec', 'cse_external_port', fallback = '80') + config.mec_protocol = parser.get('etsi_mec', 'mec_protocol', fallback = 'https') + config.mec_platform = parser.get('etsi_mec', 'mec_platform', fallback = 'mep1') + config.mec_sandbox_id = parser.get('etsi_mec', 'mec_sandbox_id', fallback = 'mec_sandbox_id') + config.fullAddress = parser.get('etsi_mec', 'fullAddress', fallback = None) + config.mec_app_instance_id = parser.get('etsi_mec', 'mec_app_instance_id', fallback = None) + + def validateConfiguration(self, config:Configuration, initial:Optional[bool] = False) -> None: + """ Validate the configuration. + + Args: + config: The configuration object. + initial: If True, the configuration is validated for the first time. + """ + + # override configuration with command line arguments + if Configuration._args_mec_enable is not None: + Configuration.mec_enable = Configuration._args_mec_enable + if Configuration._args_mec_host is not None: + Configuration.mec_host = Configuration._args_mec_host + if Configuration._args_mec_port is not None: + Configuration.mec_port = Configuration._args_mec_port + if Configuration._args_mec_protocol is not None: + Configuration.mec_protocol = Configuration._args_mec_protocol + if Configuration._args_mec_platform is not None: + Configuration.mec_platform = Configuration._args_mec_platform + if Configuration._args_mec_sandbox_id is not None: + Configuration.mec_sandbox_id = Configuration._args_mec_sandbox_id + if Configuration._cse_external_ip is not None: + Configuration.cse_external_ip = Configuration._cse_external_ip + if Configuration._cse_external_port is not None: + Configuration.cse_external_port = Configuration._cse_external_port + if Configuration.fullAddress is not None: + Configuration.fullAddress = Configuration.fullAddress + if Configuration.mec_app_instance_id is not None: + Configuration.mec_app_instance_id = Configuration.mec_app_instance_id + + config.mec_host = normalizeURL(config.mec_host) diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/entrypoint.sh b/go-apps/meep-iot-pltf/meep-acme-mn-cse/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..feba66c5143b1c5c8c6597146b4de8309b77f325 --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/entrypoint.sh @@ -0,0 +1,208 @@ +#!/bin/bash +set -e + +echo "MEEP_HOST_URL: ${MEEP_HOST_URL}" +# SERVER_IP="${MEEP_HOST_URL#http://}"; MEEP_HOST_URL="${MEEP_HOST_URL#https://}" +SERVER_IP="${MEEP_HOST_URL#http://}" +SERVER_IP="${SERVER_IP#https://}" + +echo "MEEP_HOST_URL: ${MEEP_HOST_URL}" +echo "MEC_PLATFORM=$MEEP_MEP_NAME" +echo "MEEP_SANDBOX_NAME=$MEEP_SANDBOX_NAME" + +# Other environment variables +echo "SERVER_TYPE: ${SERVER_TYPE}" +echo "SERVER_PORT: ${SERVER_PORT}" + +echo "CSE_BASE_NAME: ${CSE_BASE_NAME}" +echo "CSE_BASE_RI: ${CSE_BASE_RI}" + +echo "MQTT_ENABLE: ${MQTT_ENABLE}" +echo "MQTT_HOST: ${MQTT_HOST}" +echo "MQTT_PORT: ${MQTT_PORT}" +echo "MQTT_USERNAME: ${MQTT_USERNAME}" +echo "MQTT_PASSWORD: ${MQTT_PASSWORD}" +echo "APP_INSTANCE_ID: ${MEEP_INSTANCE_ID}" + +sleep 10 # Wait for the MEC platform to stablize and acme in cse to be ready + +# Retrieve the internal meep-mosquitto IP address +SERVICE_NAME="meep-mosquitto" +NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) +TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +# MOSQUITTO_NODE_IP_ADDRESS=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.clusterIP') +# echo "Internal IP exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $MOSQUITTO_NODE_IP_ADDRESS" + +# # Retrieve the internal meep-mosquitto port id +# MOSQUITTO_NODE_PORT=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.ports[0].targetPort') +# echo "Internal NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $MOSQUITTO_NODE_PORT" + +# # Retrieve the internal meep-acme-in-cse IP address +# SERVICE_NAME="meep-acme-in-cse" +# REMOTE_CSE_IP_ADDRESS=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.clusterIP') +# echo "Internal IP exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $REMOTE_CSE_IP_ADDRESS" + +# # Retrieve the internal meep-acme-in-cse port id +# SERVICE_NAME="meep-acme-in-cse" +# NODE_PORT_IN_CSE=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.ports[0].targetPort') +# echo "Internal NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT_IN_CSE" + +# SERVICE_NAME="meep-acme-mn-cse" +# NODE_IP=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.clusterIP') +# echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_IP" +# NODE_PORT=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.ports[0].nodePort') +# echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT" + +# The following name is the one name used to represent its block in scenario at endpoint /alt +# ACME_IN_SERVICE_NAME=${ACME_IN_SERVICE_NAME:-"monaco-telecom-meep-acme-in-cse"} +# # Fetch pod name acme in cse +# POD_NAME=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods \ +# | jq -r --arg svc "$ACME_IN_SERVICE_NAME" '.items[] | select(.metadata.labels.app == $svc) | .metadata.name' | head -n 1) +# echo "Pod name for service [$ACME_IN_SERVICE_NAME] in namespace [$NAMESPACE] is: $POD_NAME" + +# # Step 2: Get the MEEP_MEP_NAME of ACME IN CSE environment variable from the pod +# ACME_MEEP_MEP_NAME=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods/$POD_NAME \ +# | jq -r '.spec.containers[0].env[] | select(.name=="MEEP_MEP_NAME") | .value') +# echo "MEEP_MEP_NAME for service [$ACME_IN_SERVICE_NAME] in namespace [$NAMESPACE] is: $ACME_MEEP_MEP_NAME" + +if [[ ! -z "${MEEP_MEP_NAME}" ]]; then + svcPath="${MEEP_SANDBOX_NAME}/${MEEP_MEP_NAME}" +else + svcPath="${MEEP_SANDBOX_NAME}" +fi + +if [ "${MEEP_HOST_URL}" == "" ] +then + echo "$0: MEEP_HOST_URL parameter is not set" + MEEP_HOST_URL=mec-platform.etsi.org +fi + +SERVER_IP=`echo $SERVER_IP | sed -e "s/https:\/\///g"` # Remove https:// +SERVER_TYPE=${SERVER_TYPE:-"MN"} +SERVER_PORT=${SERVER_PORT:-3004} + +# MN-CSE Address + +CSE_BASE_NAME=${CSE_BASE_NAME:-"mep-cse-mn"} +CSE_BASE_RI=${CSE_BASE_RI:-"acme-mep-id-mn-cse"} +MN_CSE_FULL_ADDRESS="https://${SERVER_IP}/${svcPath}/meep-acme-mn-cse" +# REMOTE_CSE_HOST="${REMOTE_CSE_IP_ADDRESS:-"meep-acme-in-cse"}" +# REMOTE_CSE_PORT=${NODE_PORT_IN_CSE:-3003} +REMOTE_CSE_HOST=${SERVER_IP:-"meep-acme-in-cse"} +# REMOTE_CSE_PORT=443/${MEEP_SANDBOX_NAME}/${ACME_MEEP_MEP_NAME:-"monaco-telecom"}/meep-acme-in-cse +REMOTE_CSE_PORT=443 +REMOTE_SVC_PATH=${MEEP_SANDBOX_NAME}/${ACME_MEEP_MEP_NAME:-"monaco-telecom"}/meep-acme-in-cse +REMOTE_CSE_ID=${REMOTE_CSE_ID:-"laboai-id-in"} +REMOTE_CSE_NAME=${REMOTE_CSE_NAME:-"laboai-cse-in"} +ENABLE_COAP=${ENABLE_COAP:-false} +ENABLE_WS=${ENABLE_WS:-false} + +CSE_EXTERNAL_IP=${SERVER_IP} +CSE_EXTERNAL_PORT=${NODE_PORT:-443} + +# MEC Configuration +MEC_ENABLE=${MEC_ENABLE:-true} +MEC_HOST_URL=${SERVER_IP} +MEC_PLATFORM=${MEEP_MEP_NAME:-"mep1"} +MEC_SANDBOX_ID=${MEEP_SANDBOX_NAME:-"meep-sandbox"} +MEC_APP_INSTANCE_ID=${MEEP_INSTANCE_ID:-"meep-acme-mn-cse-instance"} + +# MQTT Configuration +MQTT_ENABLE=${MQTT_ENABLE:-true} +MQTT_HOST=${SERVER_IP:-"meep-mosquito"} +MQTT_PORT=443 +MQTT_USERNAME=${MQTT_USERNAME:-"acme-mn-cse"} +MQTT_PASSWORD=${MQTT_PASSWORD:-"mqtt"} + +WEBSOCK_ETPATH="/$MEC_SANDBOX_ID"${WEBSOCK_ETPATH:-"/monaco-telecom/meep-mosquitto"} + +SVC_PATH="${svcPath}/meep-acme-mn-cse" + +echo "Environment variables set:" +echo "SERVER_TYPE: ${SERVER_TYPE}" +echo "SERVER_IP: ${SERVER_IP}" +echo "SERVER_PORT: ${SERVER_PORT}" +echo "CSE_BASE_NAME: ${CSE_BASE_NAME}" +echo "CSE_BASE_RI: ${CSE_BASE_RI}" +echo "REMOTE_CSE_ID: ${REMOTE_CSE_ID}" +echo "REMOTE_CSE_NAME: ${REMOTE_CSE_NAME}" +echo "REMOTE_CSE_HOST: ${REMOTE_CSE_HOST}" +echo "REMOTE_CSE_PORT: ${REMOTE_CSE_PORT}" +echo "CSE_EXTERNAL_IP: ${CSE_EXTERNAL_IP}" +echo "CSE_EXTERNAL_PORT: ${CSE_EXTERNAL_PORT}" +echo "MQTT_ENABLE: ${MQTT_ENABLE}" +echo "MQTT_HOST: ${MQTT_HOST}" +echo "MQTT_PORT: ${MQTT_PORT}" +echo "MQTT_USERNAME: ${MQTT_USERNAME}" +echo "MQTT_PASSWORD: ${MQTT_PASSWORD}" +echo "MEC_ENABLE: ${MEC_ENABLE}" +echo "MEC_HOST_URL: ${MEC_HOST_URL}" +echo "MEC_PLATFORM: ${MEEP_MEP_NAME}" +echo "MEC_SANDBOX_ID: ${MEC_SANDBOX_ID}" +echo "ENABLE_COAP: ${ENABLE_COAP}" +echo "ENABLE_WS: ${ENABLE_WS}" +echo "WEBSOCK_ETPATH: ${WEBSOCK_ETPATH}" +echo "MN_CSE_FULL_ADDRESS: ${MN_CSE_FULL_ADDRESS}" +echo "MEC_APP_INSTANCE_ID: ${MEC_APP_INSTANCE_ID}" +echo "SVC_PATH: ${SVC_PATH}" +echo "REMOTE_SVC_PATH: ${REMOTE_SVC_PATH}" + +export SERVER_TYPE +export SERVER_IP +export SERVER_PORT +export MN_CSE_FULL_ADDRESS +export MEC_APP_INSTANCE_ID + +export CSE_BASE_NAME +export CSE_BASE_RI +export REMOTE_CSE_ID +export REMOTE_CSE_NAME +export REMOTE_CSE_HOST +export REMOTE_CSE_PORT +export REMOTE_SVC_PATH +export CSE_EXTERNAL_IP +export CSE_EXTERNAL_PORT +export ENABLE_COAP +export ENABLE_WS + +export MQTT_ENABLE +export MQTT_HOST +export MQTT_PORT +export MQTT_USERNAME +export MQTT_PASSWORD +export WEBSOCK_ETPATH + +export MEC_ENABLE +export MEC_HOST_URL +export MEC_PLATFORM +export MEC_SANDBOX_ID + +export SVC_PATH + +workdir="/usr/src/app/ACME-oneM2M-CSE" +cd "$workdir" || { echo "Directory $workdir not found"; exit 1; } +envsubst < acme.ini.in > acme.ini +cat acme.ini +python3 -m acme diff --git a/go-apps/meep-iot-pltf/meep-acme-mn-cse/requirements.txt b/go-apps/meep-iot-pltf/meep-acme-mn-cse/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..aadaf282395d072f154c19e1732c2276e686ad9b --- /dev/null +++ b/go-apps/meep-iot-pltf/meep-acme-mn-cse/requirements.txt @@ -0,0 +1,111 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile +# +blinker==1.9.0 + # via flask +cachetools==6.1.0 + # via acmecse-dev (setup.py) +cbor2==5.6.5 + # via acmecse-dev (setup.py) +certifi==2025.8.3 + # via requests +charset-normalizer==3.4.3 + # via requests +click==8.2.1 + # via flask +coapthon3-acme-cse==1.1.4 + # via acmecse-dev (setup.py) +flask==3.1.1 + # via + # acmecse-dev (setup.py) + # flask-cors +flask-cors==6.0.1 + # via acmecse-dev (setup.py) +idna==3.10 + # via requests +inquirerpy==0.3.4 + # via acmecse-dev (setup.py) +isodate==0.7.2 + # via acmecse-dev (setup.py) +itsdangerous==2.2.0 + # via flask +jinja2==3.1.6 + # via flask +kazoo==2.10.0 + # via acmecse-dev (setup.py) +linkify-it-py==2.0.3 + # via markdown-it-py +markdown-it-py[linkify,plugins]==4.0.0 + # via + # mdit-py-plugins + # rich + # textual +markupsafe==3.0.2 + # via + # flask + # jinja2 + # werkzeug +mdit-py-plugins==0.5.0 + # via markdown-it-py +mdurl==0.1.2 + # via markdown-it-py + +paho-mqtt==2.1.0 + # via acmecse-dev (setup.py) +pfzy==0.3.4 + # via inquirerpy +platformdirs==4.3.8 + # via textual +plotext==5.3.2 + # via + # acmecse-dev (setup.py) + # textual-plotext +prompt-toolkit==3.0.51 + # via inquirerpy +psycopg2-binary==2.9.10 + # via acmecse-dev (setup.py) +pygments==2.19.2 + # via + # rich + # textual +pyparsing==3.2.3 + # via rdflib +pyperclip==1.9.0 + # via acmecse-dev (setup.py) +rdflib==7.1.4 + # via acmecse-dev (setup.py) +requests==2.32.4 + # via acmecse-dev (setup.py) +rich==14.1.0 + # via + # acmecse-dev (setup.py) + # textual +shapely==2.1.1 + # via acmecse-dev (setup.py) +textual==5.2.0 + # via + # acmecse-dev (setup.py) + # textual-plotext +textual-plotext==1.0.1 + # via acmecse-dev (setup.py) +tinydb==4.8.2 + # via acmecse-dev (setup.py) +typing-extensions==4.14.1 + # via textual +uc-micro-py==1.0.3 + # via linkify-it-py +urllib3==2.5.0 + # via requests +waitress==3.0.2 + # via acmecse-dev (setup.py) +wcwidth==0.2.13 + # via prompt-toolkit +websockets==15.0.1 + # via acmecse-dev (setup.py) +werkzeug==3.1.3 + # via + # flask + # flask-cors diff --git a/go-apps/meep-iot-pltf/tinyiot-in-cse/Dockerfile b/go-apps/meep-iot-pltf/tinyiot-in-cse/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..75fe79e19653b8ff2ec10867b2b18a33751d7148 --- /dev/null +++ b/go-apps/meep-iot-pltf/tinyiot-in-cse/Dockerfile @@ -0,0 +1,97 @@ +FROM ubuntu:22.04 + +ENV SERVER_IP="" +ENV MQTT_ENABLE="" +ENV MQTT_HOST="" +ENV MQTT_PORT="" +ENV MQTT_USERNAME="" +ENV MQTT_PASSWORD="" +ENV SERVER_PORT="" +ENV SERVER_TYPE="" +ENV ENABLE_COAP="" +ENV CSE_BASE_NAME="" +ENV CSE_BASE_RI="" + +# ENV MEEP_MEP_NAME="" +# ENV HTTP_PORT="" +# ENV MQTT_SERVER="" +# ENV MQTT_PORT="" +# ENV MEC_SANDBOX_SERVER="" +# ENV MEEP_MEP_NAME="" +# ENV REGISTRAR_CSE_HOST="" +# ENV REGISTRAR_CSE_PORT="" + +RUN DEBIAN_FRONTEND=noninteractive apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + autoconf \ + bison \ + build-essential \ + cmake \ + curl \ + dos2unix \ + doxygen \ + emacs \ + expect \ + flex \ + g++ \ + gcc \ + git \ + gnutls-bin \ + iputils-ping \ + libedit2 \ + libedit-dev \ + libffi-dev \ + libglib2.0-dev \ + libgcrypt-dev \ + libjsoncpp-dev \ + libncurses5-dev \ + libpcap-dev \ + libssl-dev \ + libtool-bin \ + libtool \ + libxml2-dev \ + libxml2-utils \ + libyaml-dev \ + lsof \ + ntp \ + pkg-config \ + sudo \ + sshpass \ + tcpdump \ + tzdata \ + libcurl4-openssl-dev \ + gettext \ + jq + +WORKDIR /usr/src/app +# Make docker file from TinyIoT folder +COPY ./data/source /usr/src/app/tinyIoT + +# RUN git clone https://github.com/seslabSJU/tinyIoT.git tinyIoT + +WORKDIR /usr/src/app/tinyIoT + +# The commit is aligned wuth the changes pushed by the COPY +# The issue here is that applying patch with +#RUN git checkout 43d4c7a + +COPY ./data /usr/src/app/tinyIoT + +# RUN ls /usr/src/app/tinyIoT + +# RUN cd /usr/src/app/tinyIoT/server && ./autogen.sh && make +# RUN cd /usr/src/app/tinyIoT/server && make +# RUN cd /usr/src/app/tinyIoT +RUN cd / && chmod +x /usr/src/app/tinyIoT/entrypoint.sh +# Make sure script has execution permission +# RUN chmod +x /usr/src/app/tinyIoT/entrypoint.sh + +# Set correct ENTRYPOINT with full path +# ENTRYPOINT ["/usr/src/app/tinyIoT/entrypoint.sh"] + +# RUN cd && ls +# RUN cd /usr && ls +# RUN cd /usr/src/app && ls +# RUN ls -l /usr/src/app/tinyIoT/entrypoint.sh + +ENTRYPOINT ["/usr/src/app/tinyIoT/entrypoint.sh"] \ No newline at end of file diff --git a/go-apps/meep-iot-pltf/tinyiot-in-cse/entrypoint.sh b/go-apps/meep-iot-pltf/tinyiot-in-cse/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..31bdb8f20003da9695c4aa9cb0fdc3cf19efb029 --- /dev/null +++ b/go-apps/meep-iot-pltf/tinyiot-in-cse/entrypoint.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +# Process MEEP_HOST_URL +SERVER_IP="${MEEP_HOST_URL#http://}"; MEEP_HOST_URL="${MEEP_HOST_URL#https://}" +echo "MEEP_HOST_URL: ${MEEP_HOST_URL}" +# Other environment variables +echo "SERVER_TYPE: ${SERVER_TYPE}" +echo "SERVER_PORT: ${SERVER_PORT}" +echo "MQTT_ENABLE: ${MQTT_ENABLE}" +echo "CSE_BASE_NAME: ${CSE_BASE_NAME}" +echo "CSE_BASE_RI: ${CSE_BASE_RI}" +echo "MQTT_HOST: ${MQTT_HOST}" +echo "MQTT_PORT: ${MQTT_PORT}" +echo "MQTT_USERNAME: ${MQTT_USERNAME}" +echo "MQTT_PASSWORD: ${MQTT_PASSWORD}" + +# MEEP_SANDBOX_NAME +SERVICE_NAME="meep-tinyiot-in-cse" +# Get the namespace from the service account +NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) +# Get the token from the service account +TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +# Query the Kubernetes API to get the NodePort for the service +# NODE_PORT=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.ports[0].nodePort') +# echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT" + +if [[ ! -z "${MEEP_MEP_NAME}" ]]; then + svcPath="${MEEP_SANDBOX_NAME}/${MEEP_MEP_NAME}" +else + svcPath="${MEEP_SANDBOX_NAME}" +fi + +if [ "${MEEP_HOST_URL}" == "" ] +then + echo "$0: MEEP_HOST_URL parameter is not set" + MEEP_HOST_URL=mec-platform.etsi.org +fi + +sleep 5 # Wait for ETSI MEC Platform up and stable + +MQTT_ENABLE=${MQTT_ENABLE:-1} +MQTT_HOST=${MQTT_HOST:- "meep-mosquitto"} + +export MQTT_ENABLE +export MQTT_HOST +export MQTT_PORT +export MQTT_USERNAME +export MQTT_PASSWORD +export SERVER_PORT +export SERVER_TYPE +export SERVER_IP +export ENABLE_COAP +export CSE_BASE_NAME +export CSE_BASE_RI + +workdir="/usr/src/app/tinyIoT/server" +cd "$workdir" || { echo "Directory $workdir not found"; exit 1; } +envsubst < config.h.in > config.h +make clean +make +./server \ No newline at end of file diff --git a/go-apps/meep-iot-pltf/tinyiot-in-cse/tinyIoT b/go-apps/meep-iot-pltf/tinyiot-in-cse/tinyIoT new file mode 160000 index 0000000000000000000000000000000000000000..dd05c0791ea4003b6d96bbd045308cea2725810f --- /dev/null +++ b/go-apps/meep-iot-pltf/tinyiot-in-cse/tinyIoT @@ -0,0 +1 @@ +Subproject commit dd05c0791ea4003b6d96bbd045308cea2725810f diff --git a/go-apps/meep-iot-pltf/tinyiot-mn-cse/Dockerfile b/go-apps/meep-iot-pltf/tinyiot-mn-cse/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0407f4b7a7b005b193a3a89a7a5a1bcd44342f16 --- /dev/null +++ b/go-apps/meep-iot-pltf/tinyiot-mn-cse/Dockerfile @@ -0,0 +1,92 @@ +FROM ubuntu:22.04 + +ENV SERVER_IP="" +ENV MQTT_ENABLE="" +ENV MQTT_HOST="" +ENV MQTT_PORT="" +ENV MQTT_USERNAME="" +ENV MQTT_PASSWORD="" +ENV SERVER_PORT="" +ENV SERVER_TYPE="" +ENV ENABLE_COAP="" +ENV CSE_BASE_NAME="" +ENV CSE_BASE_RI="" +ENV REMOTE_CSE_ID="" +ENV REMOTE_CSE_NAME="" +ENV REMOTE_CSE_HOST="" +ENV REMOTE_CSE_PORT="" + +RUN DEBIAN_FRONTEND=noninteractive apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + autoconf \ + bison \ + build-essential \ + cmake \ + curl \ + dos2unix \ + doxygen \ + emacs \ + expect \ + flex \ + g++ \ + gcc \ + git \ + gnutls-bin \ + iputils-ping \ + libedit2 \ + libedit-dev \ + libffi-dev \ + libglib2.0-dev \ + libgcrypt-dev \ + libjsoncpp-dev \ + libncurses5-dev \ + libpcap-dev \ + libssl-dev \ + libtool-bin \ + libtool \ + libxml2-dev \ + libxml2-utils \ + libyaml-dev \ + lsof \ + ntp \ + pkg-config \ + sudo \ + sshpass \ + tcpdump \ + tzdata \ + libcurl4-openssl-dev \ + gettext \ + jq + +WORKDIR /usr/src/app +# Make docker file from TinyIoT folder +COPY ./data/source /usr/src/app/tinyIoT + +# RUN git clone https://github.com/seslabSJU/tinyIoT.git tinyIoT + +WORKDIR /usr/src/app/tinyIoT + +# The commit is aligned wuth the changes pushed by the COPY +# The issue here is that applying patch with +#RUN git checkout 43d4c7a + +COPY ./data /usr/src/app/tinyIoT + +# RUN ls /usr/src/app/tinyIoT + +# RUN cd /usr/src/app/tinyIoT/server && ./autogen.sh && make +# RUN cd /usr/src/app/tinyIoT/server && make +# RUN cd /usr/src/app/tinyIoT +RUN cd / && chmod +x /usr/src/app/tinyIoT/entrypoint.sh +# Make sure script has execution permission +# RUN chmod +x /usr/src/app/tinyIoT/entrypoint.sh + +# Set correct ENTRYPOINT with full path +# ENTRYPOINT ["/usr/src/app/tinyIoT/entrypoint.sh"] + +# RUN cd && ls +# RUN cd /usr && ls +# RUN cd /usr/src/app && ls +# RUN ls -l /usr/src/app/tinyIoT/entrypoint.sh + +ENTRYPOINT ["/usr/src/app/tinyIoT/entrypoint.sh"] diff --git a/go-apps/meep-iot-pltf/tinyiot-mn-cse/entrypoint.sh b/go-apps/meep-iot-pltf/tinyiot-mn-cse/entrypoint.sh new file mode 100755 index 0000000000000000000000000000000000000000..5ebfa33460fa6eb4c5f1859269e15ac13ecb9161 --- /dev/null +++ b/go-apps/meep-iot-pltf/tinyiot-mn-cse/entrypoint.sh @@ -0,0 +1,117 @@ +#!/bin/bash +set -e + +MQTT_ENABLE=${MQTT_ENABLE:-1} +# Process MEEP_HOST_URL +SERVER_IP="${MEEP_HOST_URL#http://}"; MEEP_HOST_URL="${MEEP_HOST_URL#https://}" + +# MEEP_SANDBOX_NAME +SERVICE_NAME="meep-tinyiot-in-cse" +# Get the namespace from the service account +NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) +# Get the token from the service account +TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +# Query the Kubernetes API to get the NodePort for the service +NODE_PORT_IN_CSE=$(curl -sSk \ + -H "Authorization: Bearer $TOKEN" \ + https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ + | jq -r '.spec.ports[0].nodePort') +echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT_IN_CSE" + +SERVICE_NAME="meep-mosquitto" +NODE_PORT_MEEP_MOSQUITTO=$(curl -sSk \ + -H "Authorization: Bearer $TOKEN" \ + https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ + | jq -r '.spec.ports[0].nodePort') +echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT_MEEP_MOSQUITTO" + + + +# REMOTE_CSE_HOST=$(curl -sSk \ +# -H "Authorization: Bearer $TOKEN" \ +# https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ +# | jq -r '.spec.clusterIP') + +if [[ ! -z "${MEEP_MEP_NAME}" ]]; then + svcPath="${MEEP_SANDBOX_NAME}/${MEEP_MEP_NAME}" +else + svcPath="${MEEP_SANDBOX_NAME}" +fi + +if [ "${MEEP_HOST_URL}" == "" ] +then + echo "$0: MEEP_HOST_URL parameter is not set" + MEEP_HOST_URL=mec-platform.etsi.org +fi + +sleep 5 # Wait for ETSI MEC Platform up and stable +MQTT_ENABLE=${MQTT_ENABLE:-1} +# MQTT_HOST=${MQTT_HOST:- "meep-mosquitto"} +MQTT_HOST=${SERVER_IP} +# MQTT_PORT=${MQTT_PORT:-1883} +MQTT_PORT=${NODE_PORT_MEEP_MOSQUITTO} +SERVER_IP=${SERVER_IP} +SERVER_PORT=${SERVER_PORT:-3005} +ENABLE_COAP=${ENABLE_COAP:-0} +CSE_BASE_NAME=${CSE_BASE_NAME:-"id-mn"} +CSE_BASE_RI=${CSE_BASE_RI:-"id-mn"} +SERVER_TYPE=${SERVER_TYPE:- MN_CSE} +MQTT_USERNAME=${MQTT_USERNAME:-"tinyiot-mn-cse"} +MQTT_PASSWORD=${MQTT_PASSWORD:-"mqtt"} +# REMOTE_CSE_HOST=${REMOTE_CSE_HOST:-"meep-tinyiot-in-cse"} +REMOTE_CSE_HOST="${SERVER_IP}" +REMOTE_CSE_PORT=${NODE_PORT_IN_CSE} +REMOTE_CSE_ID=${REMOTE_CSE_ID:-"TinyIoT"} +REMOTE_CSE_NAME=${REMOTE_CSE_NAME:-"tinyiot"} + +# MEC Configuration +MEC_ENABLE=${MEC_ENABLE:-1} +MEC_HOST_URL=${SERVER_IP} +MEC_PLATFORM=${MEC_PLATFORM:-"mep1"} +MEC_SANDBOX_ID=${MEEP_SANDBOX_NAME:-"meep-sandbox"} + +echo "ENVIRONMENT VARIABLES SET:" +echo "SERVER_IP: ${SERVER_IP}" +echo "SERVER_TYPE: ${SERVER_TYPE}" +echo "SERVER_PORT: ${SERVER_PORT}" +echo "CSE_BASE_NAME: ${CSE_BASE_NAME}" +echo "CSE_BASE_RI: ${CSE_BASE_RI}" +echo "REMOTE_CSE_ID: ${REMOTE_CSE_ID}" +echo "REMOTE_CSE_NAME: ${REMOTE_CSE_NAME}" +echo "REMOTE_CSE_HOST: ${REMOTE_CSE_HOST}" +echo "REMOTE_CSE_PORT: ${REMOTE_CSE_PORT}" +echo "MQTT_HOST: ${MQTT_HOST}" +echo "MQTT_ENABLE: ${MQTT_ENABLE}" +echo "MQTT_PORT: ${MQTT_PORT}" +echo "MQTT_USERNAME: ${MQTT_USERNAME}" +echo "MQTT_PASSWORD: ${MQTT_PASSWORD}" +echo "MEC_ENABLE: ${MEC_ENABLE}" +echo "MEC_HOST_URL: ${MEC_HOST_URL}" +echo "MEC_PLATFORM: ${MEC_PLATFORM}" +echo "MEC_SANDBOX_ID: ${MEC_SANDBOX_ID}" + +export MQTT_ENABLE +export MQTT_HOST +export MQTT_PORT +export MQTT_USERNAME +export MQTT_PASSWORD +export SERVER_PORT +export SERVER_TYPE +export SERVER_IP +export ENABLE_COAP +export CSE_BASE_NAME +export CSE_BASE_RI +export REMOTE_CSE_ID +export REMOTE_CSE_NAME +export REMOTE_CSE_HOST +export REMOTE_CSE_PORT +export MEC_ENABLE +export MEC_HOST_URL +export MEC_PLATFORM +export MEC_SANDBOX_ID + +workdir="/usr/src/app/tinyIoT/server" +cd "$workdir" || { echo "Directory $workdir not found"; exit 1; } +envsubst < config.h.in > config.h +make clean && make +./server \ No newline at end of file diff --git a/go-apps/meep-iot-pltf/tinyiot-mn-cse/tinyIoT b/go-apps/meep-iot-pltf/tinyiot-mn-cse/tinyIoT new file mode 160000 index 0000000000000000000000000000000000000000..dd05c0791ea4003b6d96bbd045308cea2725810f --- /dev/null +++ b/go-apps/meep-iot-pltf/tinyiot-mn-cse/tinyIoT @@ -0,0 +1 @@ +Subproject commit dd05c0791ea4003b6d96bbd045308cea2725810f diff --git a/go-apps/meep-iot/sbi/iot-sbi.go b/go-apps/meep-iot/sbi/iot-sbi.go index 253f899a014901c9bd1e0491163325cf1731594f..70f7680894a2ef81b786258db9dd3d28ecb15245 100644 --- a/go-apps/meep-iot/sbi/iot-sbi.go +++ b/go-apps/meep-iot/sbi/iot-sbi.go @@ -134,12 +134,19 @@ type TrafficRuleDescriptor struct { type InterfaceDescriptor struct { InterfaceType string - //TunnelInfo *TunnelInfo FSCOM Not supported + TunnelInfo *TunnelInfo SrcMACAddress string DstMACAddress string DstIPAddress string } +type TunnelInfo struct { + TunnelType string + TunnelDstAddress string + TunnelSrcAddress string + TunnelSpecificData string +} + type TrafficFilter struct { SrcAddress []string DstAddress []string @@ -154,6 +161,7 @@ type TrafficFilter struct { SrcTunnelPort []string DstTunnelPort []string QCI int32 + DSCP int32 TC int32 } @@ -162,24 +170,61 @@ type KeyValuePair struct { Value string } +type DownlinkInfo struct { + DownlinkTopic string + DevicePort int32 +} + +type DeviceSpecificMessageFormats struct { + EventMsgFormat *EventMsg + UplinkMsgFormat *UplinkMsg +} + +type EventMsg struct { + EventTopic string + SelectedSerializer *string + IncludeDeviceAddr bool + IncludeDeviceMetadata bool + IncludePei bool + IncludeSupi bool + IncludeImei bool + IncludeImsi bool + IncludeIccid bool + IncludeDeviceId bool +} + +type UplinkMsg struct { + UplinkTopic string + SelectedSerializer *string + IncludeDevicePort bool + IncludeDeviceAddr bool + IncludeDeviceMetadata bool + IncludePei bool + IncludeSupi bool + IncludeImei bool + IncludeImsi bool + IncludeIccid bool + IncludeDeviceId bool +} + type DeviceInfo struct { - DeviceAuthenticationInfo string - DeviceMetadata []KeyValuePair - Gpsi string - Pei string - Supi string - Msisdn string - Imei string - Imsi string - Iccid string - DeviceId string - RequestedMecTrafficRule []TrafficRuleDescriptor - RequestedIotPlatformId string - RequestedUserTransportId string - //DeviceSpecificMessageFormats *DeviceSpecificMessageFormats - //DownlinkInfo *DownlinkInfo - ClientCertificate string - Enabled bool + DeviceAuthenticationInfo string + DeviceMetadata []KeyValuePair + Gpsi string + Pei string + Supi string + Msisdn string + Imei string + Imsi string + Iccid string + DeviceId string + RequestedMecTrafficRule []TrafficRuleDescriptor + RequestedIotPlatformId string + RequestedUserTransportId string + DeviceSpecificMessageFormats *DeviceSpecificMessageFormats + DownlinkInfo *DownlinkInfo + ClientCertificate string + Enabled bool } // Init - IOT Service SBI initialization @@ -396,13 +441,13 @@ func GetDevices() (devices []DeviceInfo, err error) { if err != nil { return nil, err } - //log.Info("sbi.GetDevices: dev: ", dev) + log.Info("sbi.GetDevices: dev: ", dev) devices, err = convertDeviceInfosFromIotMgr(dev) if err != nil { return nil, err } - //log.Info("sbi.GetDevices: devices: ", devices) + log.Info("sbi.GetDevices: devices: ", devices) return devices, nil } @@ -522,7 +567,7 @@ func convertIotPlatformInfoToIotMgr(val IotPlatformInfo) (item tm.IotPlatformInf } func convertDeviceInfoFromIotMgr(dev tm.DeviceInfo) (device DeviceInfo) { - //log.Debug(">>> convertDeviceInfoFromIotMgr") + log.Debug(">>> convertDeviceInfoFromIotMgr") device = DeviceInfo{ DeviceAuthenticationInfo: dev.DeviceAuthenticationInfo, @@ -545,26 +590,120 @@ func convertDeviceInfoFromIotMgr(dev tm.DeviceInfo) (device DeviceInfo) { device.DeviceMetadata[i] = KeyValuePair{Key: k.Key, Value: k.Value} } // End of 'for' statement } - // FIXME FSCOM Add missing fileds (pointers & arrays) - //log.Debug("convertDeviceInfoFromIotMgr: device: ", device) + if len(dev.RequestedMecTrafficRule) != 0 { + device.RequestedMecTrafficRule = make([]TrafficRuleDescriptor, len(dev.RequestedMecTrafficRule)) + for i, v := range dev.RequestedMecTrafficRule { + device.RequestedMecTrafficRule[i] = TrafficRuleDescriptor{ + TrafficRuleId: v.TrafficRuleId, + FilterType: v.FilterType, + Priority: v.Priority, + Action: v.Action, + } + if len(dev.RequestedMecTrafficRule[i].TrafficFilter) != 0 { + device.RequestedMecTrafficRule[i].TrafficFilter = make([]TrafficFilter, len(dev.RequestedMecTrafficRule[i].TrafficFilter)) + for j, u := range dev.RequestedMecTrafficRule[i].TrafficFilter { + device.RequestedMecTrafficRule[i].TrafficFilter[j] = TrafficFilter{ + SrcAddress: u.SrcAddress, + DstAddress: u.DstAddress, + SrcPort: u.SrcPort, + DstPort: u.DstPort, + Protocol: u.Protocol, + Tag: u.Tag, + Uri: u.Uri, + PacketLabel: u.PacketLabel, + SrcTunnelAddress: u.SrcTunnelAddress, + TgtTunnelAddress: u.TgtTunnelAddress, + SrcTunnelPort: u.SrcTunnelPort, + DstTunnelPort: u.DstTunnelPort, + QCI: u.QCI, + DSCP: u.DSCP, + TC: u.TC, + } + } // End of 'for' statement + } + if v.DstInterface != nil { + device.RequestedMecTrafficRule[i].DstInterface = &InterfaceDescriptor{ + InterfaceType: v.DstInterface.InterfaceType, + SrcMACAddress: v.DstInterface.SrcMACAddress, + DstMACAddress: v.DstInterface.DstMACAddress, + DstIPAddress: v.DstInterface.DstIPAddress, + } + if v.DstInterface.TunnelInfo != nil { + device.RequestedMecTrafficRule[i].DstInterface.TunnelInfo = &TunnelInfo{ + TunnelType: v.DstInterface.TunnelInfo.TunnelType, + TunnelDstAddress: v.DstInterface.TunnelInfo.TunnelDstAddress, + TunnelSrcAddress: v.DstInterface.TunnelInfo.TunnelSrcAddress, + TunnelSpecificData: v.DstInterface.TunnelInfo.TunnelSpecificData, + } + } + } + } // End of 'for' statement + } + if dev.DeviceSpecificMessageFormats != nil { + var deviceSpecificMessageFormats = DeviceSpecificMessageFormats{} + if dev.DeviceSpecificMessageFormats.EventMsgFormat != nil { + deviceSpecificMessageFormats.EventMsgFormat = &EventMsg{ + EventTopic: dev.DeviceSpecificMessageFormats.EventMsgFormat.EventTopic, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer != nil { + s := *dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer + deviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer = &s + } + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat != nil { + deviceSpecificMessageFormats.UplinkMsgFormat = &UplinkMsg{ + UplinkTopic: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.UplinkTopic, + IncludeDevicePort: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDevicePort, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer != nil { + s := *dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer + deviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer = &s + } + } + device.DeviceSpecificMessageFormats = &deviceSpecificMessageFormats + } + if dev.DownlinkInfo != nil { + device.DownlinkInfo = &DownlinkInfo{ + DownlinkTopic: dev.DownlinkInfo.DownlinkTopic, + DevicePort: dev.DownlinkInfo.DevicePort, + } + } + + log.Debug("convertDeviceInfoFromIotMgr: device: ", device) return device } func convertDeviceInfosFromIotMgr(devicesList []tm.DeviceInfo) (devices []DeviceInfo, err error) { - //log.Debug(">>> convertDeviceInfosFromIotMgr") + log.Debug(">>> convertDeviceInfosFromIotMgr") devices = make([]DeviceInfo, len(devicesList)) for idx, item := range devicesList { // FIXME FSCOM Add Filter devices[idx] = convertDeviceInfoFromIotMgr(item) } // End of 'for' statement - //log.Debug("convertDeviceInfosFromIotMgr: devices: ", devices) + log.Debug("convertDeviceInfosFromIotMgr: devices: ", devices) return devices, nil } func convertDeviceInfoToIotMgr(dev DeviceInfo) (device tm.DeviceInfo) { - //log.Debug(">>> convertDeviceInfoToIotMgr") + log.Debug(">>> convertDeviceInfoToIotMgr") device = tm.DeviceInfo{ DeviceAuthenticationInfo: dev.DeviceAuthenticationInfo, @@ -587,8 +726,101 @@ func convertDeviceInfoToIotMgr(dev DeviceInfo) (device tm.DeviceInfo) { device.DeviceMetadata[i] = tm.KeyValuePair{Key: k.Key, Value: k.Value} } // End of 'for' statement } - // FIXME FSCOM Add missing fileds (pointers & arrays) - //log.Debug("convertDeviceInfoToIotMgr: device: ", device) + if len(dev.RequestedMecTrafficRule) != 0 { + device.RequestedMecTrafficRule = make([]tm.TrafficRuleDescriptor, len(dev.RequestedMecTrafficRule)) + for i, v := range dev.RequestedMecTrafficRule { + device.RequestedMecTrafficRule[i] = tm.TrafficRuleDescriptor{ + TrafficRuleId: v.TrafficRuleId, + FilterType: v.FilterType, + Priority: v.Priority, + Action: v.Action, + } + if len(dev.RequestedMecTrafficRule[i].TrafficFilter) != 0 { + device.RequestedMecTrafficRule[i].TrafficFilter = make([]tm.TrafficFilter, len(dev.RequestedMecTrafficRule[i].TrafficFilter)) + for j, u := range dev.RequestedMecTrafficRule[i].TrafficFilter { + device.RequestedMecTrafficRule[i].TrafficFilter[j] = tm.TrafficFilter{ + SrcAddress: u.SrcAddress, + DstAddress: u.DstAddress, + SrcPort: u.SrcPort, + DstPort: u.DstPort, + Protocol: u.Protocol, + Tag: u.Tag, + Uri: u.Uri, + PacketLabel: u.PacketLabel, + SrcTunnelAddress: u.SrcTunnelAddress, + TgtTunnelAddress: u.TgtTunnelAddress, + SrcTunnelPort: u.SrcTunnelPort, + DstTunnelPort: u.DstTunnelPort, + QCI: u.QCI, + DSCP: u.DSCP, + TC: u.TC, + } + } // End of 'for' statement + } + if v.DstInterface != nil { + device.RequestedMecTrafficRule[i].DstInterface = &tm.InterfaceDescriptor{ + InterfaceType: v.DstInterface.InterfaceType, + SrcMACAddress: v.DstInterface.SrcMACAddress, + DstMACAddress: v.DstInterface.DstMACAddress, + DstIPAddress: v.DstInterface.DstIPAddress, + } + if v.DstInterface.TunnelInfo != nil { + device.RequestedMecTrafficRule[i].DstInterface.TunnelInfo = &tm.TunnelInfo{ + TunnelType: v.DstInterface.TunnelInfo.TunnelType, + TunnelDstAddress: v.DstInterface.TunnelInfo.TunnelDstAddress, + TunnelSrcAddress: v.DstInterface.TunnelInfo.TunnelSrcAddress, + TunnelSpecificData: v.DstInterface.TunnelInfo.TunnelSpecificData, + } + } + } + } // End of 'for' statement + } + if dev.DeviceSpecificMessageFormats != nil { + var deviceSpecificMessageFormats = tm.DeviceSpecificMessageFormats{} + if dev.DeviceSpecificMessageFormats.EventMsgFormat != nil { + deviceSpecificMessageFormats.EventMsgFormat = &tm.EventMsg{ + EventTopic: dev.DeviceSpecificMessageFormats.EventMsgFormat.EventTopic, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer != nil { + s := *dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer + deviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer = &s + } + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat != nil { + deviceSpecificMessageFormats.UplinkMsgFormat = &tm.UplinkMsg{ + UplinkTopic: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.UplinkTopic, + IncludeDevicePort: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDevicePort, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer != nil { + s := *dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer + deviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer = &s + } + } + device.DeviceSpecificMessageFormats = &deviceSpecificMessageFormats + } + if dev.DownlinkInfo != nil { + device.DownlinkInfo = &tm.DownlinkInfo{ + DownlinkTopic: dev.DownlinkInfo.DownlinkTopic, + DevicePort: dev.DownlinkInfo.DevicePort, + } + } + log.Debug("convertDeviceInfoToIotMgr: device: ", device) return device } diff --git a/go-apps/meep-iot/server/meep-iot.go b/go-apps/meep-iot/server/meep-iot.go index aee94cfe55def0901b910fed01ab262d8de7ddac..9ebf3a1ee412ddf25ccbb88acc06b9b336d2db4e 100644 --- a/go-apps/meep-iot/server/meep-iot.go +++ b/go-apps/meep-iot/server/meep-iot.go @@ -711,6 +711,7 @@ func registerediotplatformsPOST(w http.ResponseWriter, r *http.Request) { log.Debug(">>> registerediotplatformsPOST: ", r) w.Header().Set("Content-Type", "application/json; charset=UTF-8") + var requestData IotPlatformInfo decoder := json.NewDecoder(r.Body) err := decoder.Decode(&requestData) @@ -744,14 +745,19 @@ func registerediotplatformsPOST(w http.ResponseWriter, r *http.Request) { errHandlerProblemDetails(w, "Mandatory attribute Name shall be absent in the request body.", http.StatusBadRequest) return } - if v.Type_ == nil || *v.Type_ != "MB_TOPIC_BASED" { + if v.Type_ == nil { + log.Error("Mandatory Type_ parameter shall be present") + errHandlerProblemDetails(w, "Mandatory attribute Type_ shall be present.", http.StatusBadRequest) + return + } + if *v.Type_ != "MB_TOPIC_BASED" { log.Error("Mandatory Type_ parameter shall be set to MB_TOPIC_BASED") errHandlerProblemDetails(w, "Mandatory attribute Type_ shall be set to MB_TOPIC_BASED in the request body.", http.StatusBadRequest) return } - if v.Protocol != "MQTT" && v.Protocol != "AMQP" { - log.Error("Mandatory Protocol parameter shall be set to MQTT or AMQP") - errHandlerProblemDetails(w, "Mandatory attribute Protocol shall be set to MQTT or AMQP in the request body.", http.StatusBadRequest) + if v.Protocol != "MQTT" && v.Protocol != "MQTT+WSS" && v.Protocol != "AMQP" { + log.Error("Mandatory Protocol parameter shall be set to MQTT, MQTT+WSS or AMQP") + errHandlerProblemDetails(w, "Mandatory attribute Protocol shall be set to MQTT, MQTT+WSS or AMQP in the request body.", http.StatusBadRequest) return } if v.Version == "" { @@ -777,14 +783,8 @@ func registerediotplatformsPOST(w http.ResponseWriter, r *http.Request) { } } - if requestData.CustomServicesTransportInfo == nil || len(requestData.CustomServicesTransportInfo) == 0 { - log.Error("No information to register IoT platform") - errHandlerProblemDetails(w, "No information to register IoT platform.", http.StatusInternalServerError) - return - } - - _, ok := registeredIotPlatformsMap[requestData.IotPlatformId] - if ok { + log.Debug("registerediotplatformsPOST: registeredIotPlatformsMap (before): ", registeredIotPlatformsMap) + if _, ok := registeredIotPlatformsMap[requestData.IotPlatformId]; ok { log.Error("IoT platform already created") errHandlerProblemDetails(w, "IoT platform already created.", http.StatusBadRequest) return @@ -799,11 +799,12 @@ func registerediotplatformsPOST(w http.ResponseWriter, r *http.Request) { return } registeredIotPlatformsMap[responseData.IotPlatformId] = responseData - log.Debug("registerediotplatformsPOST: new registeredIotPlatformsMap: ", registeredIotPlatformsMap) + log.Debug("registerediotplatformsPOST: registeredIotPlatformsMap (after): ", registeredIotPlatformsMap) // Prepare & send response c := convertIotPlatformInfoFromSbi(responseData) jsonResponse := convertIoTPlatformInfotoJson(&c) + w.Header().Set("Location", hostUrl.String()+basePath+"registered_iot_platforms/"+responseData.IotPlatformId) w.WriteHeader(http.StatusCreated) fmt.Fprint(w, jsonResponse) } @@ -825,6 +826,8 @@ func registereddevicesByIdDELETE(w http.ResponseWriter, r *http.Request) { return } + // FIXME FSCOM Remove entry into redist + w.WriteHeader(http.StatusNoContent) } @@ -861,6 +864,8 @@ func registereddevicesByIdGET(w http.ResponseWriter, r *http.Request) { func registereddevicesByIdPUT(w http.ResponseWriter, r *http.Request) { log.Debug(">>> registereddevicesByIdPUT: ", r) + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + errHandlerProblemDetails(w, "Not implemented yet", http.StatusBadRequest) } @@ -921,6 +926,7 @@ func registereddevicesGET(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, string(jsonResponse)) w.WriteHeader(http.StatusOK) } + func applyFiltering(devicesList []sbi.DeviceInfo, filter []string) (devices []DeviceInfo, err error) { log.Debug(">>> applyFiltering") devices, err = convertDeviceInfosFromSbi_with_filter(devicesList, filter) @@ -931,6 +937,8 @@ func applyFiltering(devicesList []sbi.DeviceInfo, filter []string) (devices []De func registereddevicesPOST(w http.ResponseWriter, r *http.Request) { log.Debug(">>> registereddevicesPOST: ", r) + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + // Read JSON input stream provided in the Request, and stores it in the bodyBytes as bytes var deviceInfo DeviceInfo bodyBytes, _ := ioutil.ReadAll(r.Body) @@ -944,13 +952,13 @@ func registereddevicesPOST(w http.ResponseWriter, r *http.Request) { log.Info("registereddevicesPOST: ", deviceInfo) // Sanity checks - if len(deviceInfo.DeviceId) == 0 { + if deviceInfo.DeviceId == "" { err = errors.New("DeviceId field shall be present") errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } if len(deviceInfo.RequestedMecTrafficRule) == 0 { - if len(deviceInfo.RequestedIotPlatformId) == 0 && len(deviceInfo.RequestedUserTransportId) == 0 { + if deviceInfo.RequestedIotPlatformId == "" && deviceInfo.RequestedUserTransportId == "" { err = errors.New("Invalid traffic rule provided") errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return @@ -966,16 +974,18 @@ func registereddevicesPOST(w http.ResponseWriter, r *http.Request) { errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) return } - log.Info("registereddevicesPOST: deviceInfoResp: ", deviceInfoResp) + devInfoResp := convertDeviceInfoFromSbi(deviceInfoResp) + log.Info("registereddevicesPOST: devInfoResp: ", devInfoResp) - jsonResponse, err := json.Marshal(deviceInfoResp) + // Prepare & send the reponse + w.Header().Set("Location", hostUrl.String()+basePath+"registered_devices/"+devInfoResp.DeviceId) + jsonResponse, err := json.Marshal(devInfoResp) if err != nil { log.Error(err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) return } log.Info("registereddevicesPOST: jsonResponse: ", string(jsonResponse)) - w.WriteHeader(http.StatusCreated) fmt.Fprint(w, string(jsonResponse)) } @@ -1057,6 +1067,7 @@ func convertIotPlatformInfoFromSbi(val sbi.IotPlatformInfo) (item IotPlatformInf } } e.Extensions = userTransportInfo.Security.Extensions + v.Security = &e } if userTransportInfo.ImplSpecificInfo != nil { v.ImplSpecificInfo = &ImplSpecificInfo{ @@ -1138,6 +1149,7 @@ func convertIotPlatformInfoToSbi(val IotPlatformInfo) (item sbi.IotPlatformInfo) } } e.Extensions = userTransportInfo.Security.Extensions + v.Security = &e } if userTransportInfo.ImplSpecificInfo != nil { v.ImplSpecificInfo = &sbi.ImplSpecificInfo{ @@ -1147,7 +1159,7 @@ func convertIotPlatformInfoToSbi(val IotPlatformInfo) (item sbi.IotPlatformInfo) } } item.UserTransportInfo = append(item.UserTransportInfo, v) - } + } // End of 'for' statement if val.CustomServicesTransportInfo != nil && len(val.CustomServicesTransportInfo) != 0 { item.CustomServicesTransportInfo = make([]sbi.TransportInfo, 0) for _, customServicesTransportInfo := range val.CustomServicesTransportInfo { @@ -1176,7 +1188,7 @@ func convertIotPlatformInfoToSbi(val IotPlatformInfo) (item sbi.IotPlatformInfo) v.Endpoint = &e } item.CustomServicesTransportInfo = append(item.CustomServicesTransportInfo, v) - } + } // End of 'for' statement } return item @@ -1204,9 +1216,116 @@ func convertDeviceInfoFromSbi(dev sbi.DeviceInfo) (device DeviceInfo) { device.DeviceMetadata[i] = KeyValuePair{Key: k.Key, Value: k.Value} } // End of 'for' statement } - // FIXME FSCOM Add missing fileds (pointers & arrays) - //log.Debug("convertDeviceInfosFromSbi: devices: ", devices) + if len(dev.RequestedMecTrafficRule) != 0 { + device.RequestedMecTrafficRule = make([]TrafficRuleDescriptor, len(dev.RequestedMecTrafficRule)) + for i, v := range dev.RequestedMecTrafficRule { + device.RequestedMecTrafficRule[i] = TrafficRuleDescriptor{ + TrafficRuleId: v.TrafficRuleId, + FilterType: v.FilterType, + Priority: v.Priority, + Action: v.Action, + } + if len(dev.RequestedMecTrafficRule[i].TrafficFilter) != 0 { + device.RequestedMecTrafficRule[i].TrafficFilter = make([]TrafficFilter, len(dev.RequestedMecTrafficRule[i].TrafficFilter)) + for j, u := range dev.RequestedMecTrafficRule[i].TrafficFilter { + device.RequestedMecTrafficRule[i].TrafficFilter[j] = TrafficFilter{ + SrcAddress: u.SrcAddress, + DstAddress: u.DstAddress, + SrcPort: u.SrcPort, + DstPort: u.DstPort, + Protocol: u.Protocol, + Tag: u.Tag, + Uri: u.Uri, + PacketLabel: u.PacketLabel, + SrcTunnelAddress: u.SrcTunnelAddress, + TgtTunnelAddress: u.TgtTunnelAddress, + SrcTunnelPort: u.SrcTunnelPort, + DstTunnelPort: u.DstTunnelPort, + QCI: u.QCI, + DSCP: u.DSCP, + TC: u.TC, + } + } // End of 'for' statement + } + if v.DstInterface != nil { + device.RequestedMecTrafficRule[i].DstInterface = &InterfaceDescriptor{ + InterfaceType: v.DstInterface.InterfaceType, + SrcMACAddress: v.DstInterface.SrcMACAddress, + DstMACAddress: v.DstInterface.DstMACAddress, + DstIPAddress: v.DstInterface.DstIPAddress, + } + if v.DstInterface.TunnelInfo != nil { + device.RequestedMecTrafficRule[i].DstInterface.TunnelInfo = &TunnelInfo{ + TunnelType: v.DstInterface.TunnelInfo.TunnelType, + TunnelDstAddress: v.DstInterface.TunnelInfo.TunnelDstAddress, + TunnelSrcAddress: v.DstInterface.TunnelInfo.TunnelSrcAddress, + TunnelSpecificData: v.DstInterface.TunnelInfo.TunnelSpecificData, + } + } + } + } // End of 'for' statement + } + if dev.DeviceSpecificMessageFormats != nil { + var deviceSpecificMessageFormats = DeviceSpecificMessageFormats{} + if dev.DeviceSpecificMessageFormats.EventMsgFormat != nil { + deviceSpecificMessageFormats.EventMsgFormat = &EventMsg{ + EventTopic: dev.DeviceSpecificMessageFormats.EventMsgFormat.EventTopic, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer != nil { + var s SerializerType + if *dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer == "JSON" { + s = JSON + } else if *dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer == "XML" { + s = XML + } else { + s = PROTOBUF3 + } + deviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer = &s + } + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat != nil { + deviceSpecificMessageFormats.UplinkMsgFormat = &UplinkMsg{ + UplinkTopic: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.UplinkTopic, + IncludeDevicePort: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDevicePort, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer != nil { + var s SerializerType + if *dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer == "JSON" { + s = JSON + } else if *dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer == "XML" { + s = XML + } else { + s = PROTOBUF3 + } + deviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer = &s + } + } + device.DeviceSpecificMessageFormats = &deviceSpecificMessageFormats + } + if dev.DownlinkInfo != nil { + device.DownlinkInfo = &DownlinkInfo{ + DownlinkTopic: dev.DownlinkInfo.DownlinkTopic, + DevicePort: dev.DownlinkInfo.DevicePort, + } + } + //log.Debug("convertDeviceInfosFromSbi: devices: ", devices) return device } @@ -1284,8 +1403,101 @@ func convertDeviceInfoToSbi(dev DeviceInfo) (device sbi.DeviceInfo) { device.DeviceMetadata[i] = sbi.KeyValuePair{Key: k.Key, Value: k.Value} } // End of 'for' statement } - // FIXME FSCOM Add missing fileds (pointers & arrays) - //log.Debug("convertDeviceInfoToSbi: devices: ", devices) + if len(dev.RequestedMecTrafficRule) != 0 { + device.RequestedMecTrafficRule = make([]sbi.TrafficRuleDescriptor, len(dev.RequestedMecTrafficRule)) + for i, v := range dev.RequestedMecTrafficRule { + device.RequestedMecTrafficRule[i] = sbi.TrafficRuleDescriptor{ + TrafficRuleId: v.TrafficRuleId, + FilterType: v.FilterType, + Priority: v.Priority, + Action: v.Action, + } + if len(dev.RequestedMecTrafficRule[i].TrafficFilter) != 0 { + device.RequestedMecTrafficRule[i].TrafficFilter = make([]sbi.TrafficFilter, len(dev.RequestedMecTrafficRule[i].TrafficFilter)) + for j, u := range dev.RequestedMecTrafficRule[i].TrafficFilter { + device.RequestedMecTrafficRule[i].TrafficFilter[j] = sbi.TrafficFilter{ + SrcAddress: u.SrcAddress, + DstAddress: u.DstAddress, + SrcPort: u.SrcPort, + DstPort: u.DstPort, + Protocol: u.Protocol, + Tag: u.Tag, + Uri: u.Uri, + PacketLabel: u.PacketLabel, + SrcTunnelAddress: u.SrcTunnelAddress, + TgtTunnelAddress: u.TgtTunnelAddress, + SrcTunnelPort: u.SrcTunnelPort, + DstTunnelPort: u.DstTunnelPort, + QCI: u.QCI, + DSCP: u.DSCP, + TC: u.TC, + } + } // End of 'for' statement + } + if v.DstInterface != nil { + device.RequestedMecTrafficRule[i].DstInterface = &sbi.InterfaceDescriptor{ + InterfaceType: v.DstInterface.InterfaceType, + SrcMACAddress: v.DstInterface.SrcMACAddress, + DstMACAddress: v.DstInterface.DstMACAddress, + DstIPAddress: v.DstInterface.DstIPAddress, + } + if v.DstInterface.TunnelInfo != nil { + device.RequestedMecTrafficRule[i].DstInterface.TunnelInfo = &sbi.TunnelInfo{ + TunnelType: v.DstInterface.TunnelInfo.TunnelType, + TunnelDstAddress: v.DstInterface.TunnelInfo.TunnelDstAddress, + TunnelSrcAddress: v.DstInterface.TunnelInfo.TunnelSrcAddress, + TunnelSpecificData: v.DstInterface.TunnelInfo.TunnelSpecificData, + } + } + } + } // End of 'for' statement + } + if dev.DeviceSpecificMessageFormats != nil { + var deviceSpecificMessageFormats = sbi.DeviceSpecificMessageFormats{} + if dev.DeviceSpecificMessageFormats.EventMsgFormat != nil { + deviceSpecificMessageFormats.EventMsgFormat = &sbi.EventMsg{ + EventTopic: dev.DeviceSpecificMessageFormats.EventMsgFormat.EventTopic, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.EventMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer != nil { + var s string = string(*dev.DeviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer) + deviceSpecificMessageFormats.EventMsgFormat.SelectedSerializer = &s + } + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat != nil { + deviceSpecificMessageFormats.UplinkMsgFormat = &sbi.UplinkMsg{ + UplinkTopic: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.UplinkTopic, + IncludeDevicePort: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDevicePort, + IncludeDeviceAddr: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceAddr, + IncludeDeviceMetadata: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceMetadata, + IncludePei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludePei, + IncludeSupi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeSupi, + IncludeImei: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImei, + IncludeImsi: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeImsi, + IncludeIccid: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeIccid, + IncludeDeviceId: dev.DeviceSpecificMessageFormats.UplinkMsgFormat.IncludeDeviceId, + } + if dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer != nil { + var s string = string(*dev.DeviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer) + deviceSpecificMessageFormats.UplinkMsgFormat.SelectedSerializer = &s + } + } + device.DeviceSpecificMessageFormats = &deviceSpecificMessageFormats + } + if dev.DownlinkInfo != nil { + device.DownlinkInfo = &sbi.DownlinkInfo{ + DownlinkTopic: dev.DownlinkInfo.DownlinkTopic, + DevicePort: dev.DownlinkInfo.DevicePort, + } + } + //log.Debug("convertDeviceInfoToSbi: devices: ", devices) return device } diff --git a/go-apps/meep-mosquitto/entrypoint.sh b/go-apps/meep-mosquitto/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..e0a3e58eba557b71d6c70e225229a64585008ba6 --- /dev/null +++ b/go-apps/meep-mosquitto/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e +SERVICE_NAME="meep-mosquitto" +# Get the namespace from the service account +NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) +# Get the token from the service account +TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) +echo "Token: $TOKEN" +# Query the Kubernetes API to get the NodePort for the service +NODE_PORT=$(curl -sSk \ + -H "Authorization: Bearer $TOKEN" \ + https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/$SERVICE_NAME \ + | jq -r '.spec.ports[0].nodePort') +echo "External NodePort exposed for service [$SERVICE_NAME] in namespace [$NAMESPACE] is: $NODE_PORT" +exec /usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf diff --git a/go-apps/meep-sandbox-ctrl/server/app-ctrl.go b/go-apps/meep-sandbox-ctrl/server/app-ctrl.go index a798cd5133979a01a1885a83206bb5a789ca7a41..0e9cdc8944351221def4f8d0dc08e8fa56bdd266 100644 --- a/go-apps/meep-sandbox-ctrl/server/app-ctrl.go +++ b/go-apps/meep-sandbox-ctrl/server/app-ctrl.go @@ -250,18 +250,23 @@ func getScenarioAppInstanceList(activeModel *mod.Model) ([]*apps.Application, er var appList []*apps.Application if activeModel != nil { - // Get active scenario node names - appNames := activeModel.GetNodeNames(mod.NodeTypeEdgeApp) - for _, appName := range appNames { - // Get scenario Process & context - proc, ctx, err := getScenarioProcess(appName, activeModel) - if err != nil { - log.Error(err.Error()) + // Get all processes + processes := activeModel.GetProcesses(nil) + for _, proc := range processes.Processes { + // Only handle EDGE-APP for duplicates + if proc.Type_ != mod.NodeTypeEdgeApp { + continue + } + + // Get context by ID + ctx := activeModel.GetNodeContextById(proc.Id) + if ctx == nil { + log.Error("Failed to get context for process ID: " + proc.Id) continue } // Create app instance - app, err := createAppInstance(proc, ctx) + app, err := createAppInstance(&proc, ctx) if err != nil { log.Error(err.Error()) continue diff --git a/go-apps/meep-sss/api/swagger.yaml b/go-apps/meep-sss/api/swagger.yaml index ab90949cdd44215806e980964d04ce377a31237f..2f1edce63769b19412190b947baf1ba094f5951f 100644 --- a/go-apps/meep-sss/api/swagger.yaml +++ b/go-apps/meep-sss/api/swagger.yaml @@ -791,7 +791,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/SensorStatusSubscription' + $ref: '#/components/schemas/SensorDataSubscription' x-content-type: application/json "400": description: "Bad Request: used to indicate that incorrect parameters were\ @@ -1577,5 +1577,5 @@ components: status_data_subscriptionId_body: type: object properties: - SensorStatusSubscription: + SensorDataSubscription: $ref: '#/components/schemas/SensorDataSubscription' diff --git a/go-apps/meep-sss/go.mod b/go-apps/meep-sss/go.mod index c46ed40ecc81f0c5251b552ca09bac4126b5a3c6..263642e7913089ffeff17196b860133c2c362ae5 100644 --- a/go-apps/meep-sss/go.mod +++ b/go-apps/meep-sss/go.mod @@ -15,7 +15,8 @@ require ( github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-mq v0.0.0 github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis v0.0.0 github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sandbox-ctrl-client v0.0.0 - github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client v0.0.0 + github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client v0.0.0 + github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-iot-client v0.0.0 github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-subscriptions v0.0.0 // indirect github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-swagger-api-mgr v0.0.0 github.com/gorilla/handlers v1.5.1 @@ -37,6 +38,7 @@ replace ( github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis => ../../go-packages/meep-redis github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sandbox-ctrl-client => ../../go-packages/meep-sandbox-ctrl-client github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client => ../../go-packages/meep-service-mgmt-client + github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-iot-client => ../../go-packages/meep-iot-client github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-subscriptions => ../../go-packages/meep-subscriptions github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-swagger-api-mgr => ../../go-packages/meep-swagger-api-mgr github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-websocket => ../../go-packages/meep-websocket diff --git a/go-apps/meep-sss/go.sum b/go-apps/meep-sss/go.sum index d2eacd2e5c2eaef6bcf0775f09b7b004b9fb2d35..7aa8d1dcc421c191638defaaa3316e9e99cbfdde 100644 --- a/go-apps/meep-sss/go.sum +++ b/go-apps/meep-sss/go.sum @@ -1,7 +1,39 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/InterDigitalInc/AdvantEDGE v1.9.2 h1:CAcF+bn5m0Va2mHFL2lE4awU/kjuF6CjC05phiz8vnk= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/RyanCarrier/dijkstra v0.0.0-20190726134004-b51cadb5ae52 h1:trnwuu/Q8T59kgRjXcSDBODnyZP9wes+bnLn0lx4PgM= @@ -41,8 +73,12 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -63,7 +99,9 @@ github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6r github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= @@ -74,6 +112,9 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= @@ -92,15 +133,27 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -113,14 +166,28 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -160,6 +227,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -170,6 +238,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -328,12 +398,19 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -346,17 +423,39 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -370,27 +469,52 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -404,32 +528,60 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -437,44 +589,134 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -499,7 +741,14 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/go-apps/meep-sss/sbi/sss-sbi.go b/go-apps/meep-sss/sbi/sss-sbi.go index 28fee729720a7e066762cdad2822d9862d854603..cb1ebe404c4b0f3f7ea3c69c339c3dde0b290a5f 100644 --- a/go-apps/meep-sss/sbi/sss-sbi.go +++ b/go-apps/meep-sss/sbi/sss-sbi.go @@ -17,6 +17,8 @@ package sbi import ( + "encoding/json" + "errors" "sync" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" @@ -28,6 +30,14 @@ import ( const moduleName string = "meep-sss-sbi" +type IotPlatformInfo struct { + Host string + Port int + CseName string + Id string + Protocol string +} + type Point struct { Latitude float64 Longitude float64 @@ -53,6 +63,31 @@ type SensorStatusInfo struct { ErrorInformation string } +type SensorData struct { + SensorIdentifier string + Data string + DataFormat string + DataUnitOfMeasure string + DataTimestamp *TimeStamp +} + +type SensorDiscoveryEventSubscription struct { + SubscriptionType string + SensorInfoList []string + GeographicalArea []AreaInfo +} + +type AreaInfo struct { + Shape string + Points []Point + Radius int32 +} + +type TimeStamp struct { + Seconds int32 + NanoSeconds int32 +} + type SbiCfg struct { ModuleName string SandboxName string @@ -164,12 +199,17 @@ func Init(cfg SbiCfg) (err error) { } // Connect to SSS Manager - sbi.sssMgr, err = tm.NewSssMgr(sbi.moduleName, sbi.sandboxName, sbi.protocol, sbi.host, sbi.port, sbi.hostId, sbi.name, sbi.discoveryNotify, sbi.statusNotify, sbi.dataNotify) - if err != nil { - log.Error("Failed connection to SSS Manager: ", err) - return err + if sbi.port != 0 { + sbi.sssMgr, err = tm.NewSssMgr(sbi.moduleName, sbi.sandboxName, sbi.protocol, sbi.host, sbi.port, sbi.hostId, sbi.name, sbi.discoveryNotify, sbi.statusNotify, sbi.dataNotify) + if err != nil { + log.Error("Failed connection to SSS Manager: ", err) + return err + } + log.Info("Connected to SSS Manager") + } else { + sbi.sssMgr = nil + log.Warn("No IoTPlatform registered") } - log.Info("Connected to SSS Manager") // Initialize service processActiveScenarioUpdate() @@ -237,6 +277,23 @@ func Stop() (err error) { return nil } +func UpdatePlatformList(iotPlatformInfoList []IotPlatformInfo) (err error) { // FIXME FSCOM To be refined to support multiple IoT platform registered + // Connect to SSS Manager + if sbi.sssMgr != nil { + log.Warn("UpdatePlatformList: IoTPlatform already registered") + return nil + } + + sbi.sssMgr, err = tm.NewSssMgr(sbi.moduleName, sbi.sandboxName, iotPlatformInfoList[0].Protocol, iotPlatformInfoList[0].Host, iotPlatformInfoList[0].Port, iotPlatformInfoList[0].Id, iotPlatformInfoList[0].CseName, sbi.discoveryNotify, sbi.statusNotify, sbi.dataNotify) + if err != nil { + log.Error("UpdatePlatformList: Failed connection to SSS Manager: ", err) + return err + } + log.Info("UpdatePlatformList: Connected to SSS Manager") + + return nil +} + // Message Queue handler func msgHandler(msg *mq.Msg, userData interface{}) { switch msg.Message { @@ -292,6 +349,11 @@ func processActiveScenarioUpdate() { func SensorDiscoveryInfoAll() (sensors []SensorDiscoveryInfo, err error) { log.Info("sbi.SensorDiscoveryInfoAll") + if sbi.sssMgr == nil { + err := errors.New("No IoT platform registered") + return nil, err + } + s, err := sbi.sssMgr.SensorDiscoveryInfoAll() if err != nil { return nil, err @@ -307,6 +369,11 @@ func SensorDiscoveryInfoAll() (sensors []SensorDiscoveryInfo, err error) { func GetSensorStatus(sensorIdentifier string) (status SensorStatusInfo, err error) { log.Info("sbi.GetSensorStatus") + if sbi.sssMgr == nil { + err := errors.New("No IoT platform registered") + return status, err + } + s, err := sbi.sssMgr.GetSensor(sensorIdentifier) if err != nil { return status, err @@ -316,7 +383,7 @@ func GetSensorStatus(sensorIdentifier string) (status SensorStatusInfo, err erro sensor := convertSensorDiscoveryInfoFromSssMgr(s) log.Info("sbi.GetSensorStatus: sensors: ", sensor) - // FIXME FSCOM CHeck status of the sensor??? + // FIXME FSCOM Check status of the sensor??? status = SensorStatusInfo{ SensorIdentifier: sensor.SensorIdentifier, SensorStatusType: "ONLINE", @@ -326,6 +393,64 @@ func GetSensorStatus(sensorIdentifier string) (status SensorStatusInfo, err erro return status, nil } +func GetSensorData(sensorIdentifier string) (data SensorData, err error) { + log.Info("sbi.GetSensorData") + + if sbi.sssMgr == nil { + err := errors.New("No IoT platform registered") + return data, err + } + + s, err := sbi.sssMgr.GetSensor(sensorIdentifier) + if err != nil { + return data, err + } + log.Info("sbi.GetSensorData: s: ", s) + log.Info("sbi.GetSensorData: s.SensorPropertyList: ", s.SensorPropertyList) + log.Info("sbi.GetSensorData: s.SensorCharacteristicList: ", s.SensorCharacteristicList) + log.Info("sbi.GetSensorData: s.Flex: ", s.Flex) + + sensor := convertSensorDiscoveryInfoFromSssMgr(s) + log.Info("sbi.GetSensorData: sensors: ", sensor) + + data = SensorData{ // FIXME FSCOM Review content of SensorData. What is the mapping oneM2M? + SensorIdentifier: sensor.SensorIdentifier, + //Data: string(sensor.SensorCharacteristicList), + DataFormat: "", + DataUnitOfMeasure: "", + DataTimestamp: nil, + } + j, err := json.Marshal(sensor.SensorCharacteristicList) + if err != nil { + log.Warn("sbi.GetSensorData: j: ", j) + return data, err + } + data.Data = string(j) + + return data, nil +} + +func ProcessSensorDiscoveryEventSubscription(sensorDiscoveryEventSubscription SensorDiscoveryEventSubscription) (err error) { + log.Info("sbi.processSensorDiscoveryEventSubscription: ", sensorDiscoveryEventSubscription) + + if sbi.sssMgr == nil { + err = errors.New("No IoT platform registered") + return err + } + + sub := tm.SensorDiscoveryEventSubscription{ + SubscriptionType: sensorDiscoveryEventSubscription.SubscriptionType, + SensorInfoList: sensorDiscoveryEventSubscription.SensorInfoList, + } + // TODO FSCOM Add GeographicalArea field & create a convert function + err = sbi.sssMgr.ProcessSensorDiscoveryEventSubscription(sub) + if err != nil { + return err + } + + return nil +} + func convertSensorDiscoveryInfoListFromSssMgr(s []tm.SensorDiscoveryInfo) (sensors []SensorDiscoveryInfo) { sensors = make([]SensorDiscoveryInfo, len(s)) for i, val := range s { @@ -336,14 +461,18 @@ func convertSensorDiscoveryInfoListFromSssMgr(s []tm.SensorDiscoveryInfo) (senso } func convertSensorDiscoveryInfoFromSssMgr(s tm.SensorDiscoveryInfo) (sensor SensorDiscoveryInfo) { + log.Info(">>> sbi.convertSensorDiscoveryInfoFromSssMgr: ", s) + sensor = SensorDiscoveryInfo{ SensorIdentifier: s.SensorIdentifier, SensorType: s.SensorType, } + log.Info("sbi.convertSensorDiscoveryInfoFromSssMgr: len(s.SensorPropertyList): ", len(s.SensorPropertyList)) if len(s.SensorPropertyList) != 0 { sensor.SensorPropertyList = make([]string, len(s.SensorPropertyList)) copy(sensor.SensorPropertyList, s.SensorPropertyList) } + log.Info("sbi.convertSensorDiscoveryInfoFromSssMgr: len(s.SensorCharacteristicList): ", len(s.SensorCharacteristicList)) if len(s.SensorCharacteristicList) != 0 { sensor.SensorCharacteristicList = make([]SensorCharacteristic, len(s.SensorCharacteristicList)) for i, val := range s.SensorCharacteristicList { @@ -354,6 +483,19 @@ func convertSensorDiscoveryInfoFromSssMgr(s tm.SensorDiscoveryInfo) (sensor Sens } } // End of 'for' statement } + log.Info("sbi.convertSensorDiscoveryInfoFromSssMgr: len(s.Flex): ", len(s.Flex)) + if len(s.Flex) != 0 { + sensor.SensorCharacteristicList = make([]SensorCharacteristic, len(s.Flex)) + i := 0 + for key, val := range s.Flex { + sensor.SensorCharacteristicList[i] = SensorCharacteristic{ + CharacteristicName: key, + CharacteristicValue: val.(string), + CharacteristicUnitOfMeasure: nil, + } + i += 1 + } // End of 'for' statement + } if s.SensorPosition != nil { sensor.SensorPosition = &Point{ Latitude: s.SensorPosition.Latitude, @@ -361,5 +503,6 @@ func convertSensorDiscoveryInfoFromSssMgr(s tm.SensorDiscoveryInfo) (sensor Sens } } + log.Info("<<< sbi.convertSensorDiscoveryInfoFromSssMgr: ", sensor) return sensor } diff --git a/go-apps/meep-sss/server/api_sensor_data_lookup.go b/go-apps/meep-sss/server/api_sensor_data_lookup.go index 191b7a8a65f79479b8fdc54e489f558711f37d1c..194094776f7dee6bd0dd9948340e687b810f1c46 100644 --- a/go-apps/meep-sss/server/api_sensor_data_lookup.go +++ b/go-apps/meep-sss/server/api_sensor_data_lookup.go @@ -14,6 +14,5 @@ import ( ) func SensorDataLookupGET(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + sensorDataLookupGET(w, r) } diff --git a/go-apps/meep-sss/server/convert.go b/go-apps/meep-sss/server/convert.go index 0671a0cc1262fb012aeb122daea7b3b15e838dfb..1dc26516ea1cfbc7eb8bac8db440e08b847c9e28 100644 --- a/go-apps/meep-sss/server/convert.go +++ b/go-apps/meep-sss/server/convert.go @@ -41,6 +41,15 @@ func convertSensorDiscoveryInfoListToJson(sensors []SensorDiscoveryInfo) string return string(jsonInfo) } +func convertSensorDataToJson(data SensorData) string { + jsonInfo, err := json.Marshal(data) + if err != nil { + log.Error(err.Error()) + return "" + } + return string(jsonInfo) +} + func convertSensorStatusToJson(status SensorStatusInfo) string { jsonInfo, err := json.Marshal(status) if err != nil { diff --git a/go-apps/meep-sss/server/meep-sss.go b/go-apps/meep-sss/server/meep-sss.go index d07e22f94a1efdee4ce4562fd1ea6c6d15cb002d..d4d96bbd7cbf4a6287a8fef1e2a2ecc6e9d6d1c9 100644 --- a/go-apps/meep-sss/server/meep-sss.go +++ b/go-apps/meep-sss/server/meep-sss.go @@ -35,6 +35,7 @@ import ( asc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-support-client" dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr" httpLog "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-http-logger" + iot "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-iot-client" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" met "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-metrics" redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis" @@ -56,6 +57,8 @@ const defaultScopeOfLocality = "MEC_SYSTEM" const defaultConsumedLocalOnly = true const appTerminationPath = "notifications/mec011/appTermination" +const iotBasePath = "iots/v1" + var redisAddr string = "meep-redis-master.default.svc.cluster.local:6379" var influxAddr string = "http://meep-influxdb.default.svc.cluster.local:8086" var sbxCtrlUrl string = "http://meep-sandbox-ctrl" @@ -88,8 +91,10 @@ var appEnablementServiceId string var appSupportClient *asc.APIClient var svcMgmtClient *smc.APIClient var sbxCtrlClient *scc.APIClient +var iotClient *iot.APIClient -var registrationTicker *time.Ticker +var registrationTicker *time.Ticker = nil +var iotPlatfomrDiscoveryTicker *time.Ticker = nil const SENS_DISCOVERY = "SensorDiscoveryEventSubscription" const SENS_STATUS = "SensorStatusSubscription" @@ -112,12 +117,6 @@ var mutex sync.Mutex var expiryTicker *time.Ticker var nextSubscriptionIdAvailable int -var iot_platform_address string = "lab-oai.etsi.org" -var iot_platform_port int = 31110 -var cse_name string = "laboai-acme-ic-cse" -var iot_platform_id string = "7feaadbb0400" -var iot_platform_protocol string = "REST_HTTP" - func getAppInstanceId() (id string, err error) { var appInfo scc.ApplicationInfo appInfo.Id = instanceId @@ -390,15 +389,16 @@ func Init() (err error) { Locality: locality, ScenarioNameCb: updateStoreName, CleanUpCb: cleanUp, - Protocol: iot_platform_protocol, - Host: iot_platform_address, - Port: iot_platform_port, - Name: cse_name, - HostId: iot_platform_id, + Protocol: "", + Host: "", + Port: 0, + Name: "", + HostId: "", DiscoveryNotify: discoveryNotify, StatusNotify: statusNotify, DataNotify: dataNotify, } + if mepName != defaultMepName { sbiCfg.MepName = mepName } @@ -409,6 +409,14 @@ func Init() (err error) { } log.Info("SBI Initialized") + iotClientCfg := iot.NewConfiguration() + iotClientCfg.BasePath = "http://" + mepName + "-meep-iot/" + iotBasePath + iotClient = iot.NewAPIClient(iotClientCfg) + if iotClient == nil { + return errors.New("Failed to create IoT REST API client") + } + log.Info("Create IoT REST API client") + // Create App Enablement REST clients if appEnablementEnabled { // Create Sandbox Controller client @@ -445,6 +453,9 @@ func Init() (err error) { // Run - Start SSS func Run() (err error) { + // Start Iot platfomr discovery ticker + startIotPlatformDiscoveryTicker() + // Start MEC Service registration ticker if appEnablementEnabled { startRegistrationTicker() @@ -454,6 +465,9 @@ func Run() (err error) { // Stop - Stop SSS func Stop() (err error) { + // Stop Iot platfomr discovery ticker + stopIotPlatfomrDiscoveryTicker() + // Stop MEC Service registration ticker if appEnablementEnabled { stopRegistrationTicker() @@ -546,6 +560,65 @@ func stopRegistrationTicker() { } } +func startIotPlatformDiscoveryTicker() { + // Make sure ticker is not running + if iotPlatfomrDiscoveryTicker != nil { + log.Warn("IoT platform discovery ticker already running") + return + } + + // Wait a few seconds to allow App Enablement Service to start. + // This is done to avoid the default 20 second TCP socket connect timeout + // if the App Enablement Service is not yet running. + log.Info("Waiting stable state start") + time.Sleep(5 * time.Second) + + // Start registration ticker + iotPlatfomrDiscoveryTicker = time.NewTicker(time.Duration(60) * time.Second) + go func() { + for range iotPlatfomrDiscoveryTicker.C { + log.Info("startIotPlatformDiscoveryTicker: Launch IoT platform discovery") + iotPlatformInfoList, _, err := iotClient.RegIotPlatApi.RegisterediotplatformsGET(context.TODO(), nil) + if err != nil { + log.Error("Failed to initiate the : IoT platform discovery: ", err) + } else { + l := make([]sbi.IotPlatformInfo, 0) + for _, v := range iotPlatformInfoList { + if v.Enabled { + var p = sbi.IotPlatformInfo{ + /* For HTTP */ + Host: v.CustomServicesTransportInfo[0].Endpoint.Addresses[0].Host, + Port: int(v.CustomServicesTransportInfo[0].Endpoint.Addresses[0].Port), + CseName: v.CustomServicesTransportInfo[0].Name, + Id: v.IotPlatformId, + Protocol: v.CustomServicesTransportInfo[0].Protocol, + /* For MQTT, issues :( send req + Host: v.UserTransportInfo[0].Endpoint.Addresses[0].Host, + Port: int(v.UserTransportInfo[0].Endpoint.Addresses[0].Port), + CseName: v.UserTransportInfo[0].Name, + Id: v.IotPlatformId, + Protocol: v.UserTransportInfo[0].Protocol, + */ + } + l = append(l, p) + } // else, skip it + } // End of 'for'statement + if len(l) != 0 { + _ = sbi.UpdatePlatformList(l) + } + } + } // End of 'for' statement + }() +} + +func stopIotPlatfomrDiscoveryTicker() { + if iotPlatfomrDiscoveryTicker != nil { + log.Info("Stopping IoT platform discovery ticker") + iotPlatfomrDiscoveryTicker.Stop() + iotPlatfomrDiscoveryTicker = nil + } +} + func cleanUp() { log.Info("Terminate all") @@ -606,14 +679,31 @@ func sensorDiscoveryLookupGET(w http.ResponseWriter, r *http.Request) { log.Debug("sensorDiscoveryLookupGET: q: ", q) if len(q) == 0 { err := errors.New("Invalid query parameters") + log.Error("sensorDiscoveryLookupGET: ", err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } else if _, ok := q["type"]; !ok { + err := errors.New("Invalid query parameters: 'type' parameter is mandatory") + log.Error("sensorDiscoveryLookupGET: ", err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } else if len(q["type"][0]) == 0 { + err := errors.New("Invalid query parameters: Wrong 'type' parameter value") + log.Error("sensorDiscoveryLookupGET: ", err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } else if _, ok := q["sensorPropertyList"]; !ok { + err := errors.New("Invalid query parameters: 'sensorPropertyList' parameter is mandatory, at leasr one item") + log.Error("sensorDiscoveryLookupGET: ", err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } else if len(q["sensorPropertyList"][0]) == 0 { + err := errors.New("Invalid query parameters: Wrong 'sensorPropertyList' parameter value") + log.Error("sensorDiscoveryLookupGET: ", err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } - log.Debug("sensorDiscoveryLookupGET: q[type][0]: ", q["type"][0]) - //log.Debug("sensorDiscoveryLookupGET: q[sensorCharacteristicList][0]: ", q["sensorCharacteristicList"][0]) - //log.Debug("sensorDiscoveryLookupGET: type(q[sensorCharacteristicList][0]): ", reflect.TypeOf(q["sensorCharacteristicList"][0])) - //q: map[geographicalArea:[[object Object]] sensorCharacteristicList:[[object Object]] sensorPropertyList:[string1,string2] type:[string]]","time":"2025-02-04T08:35:35Z"} - // /sens/v1/queries/sensor_discovery?type=4&sensorPropertyList=%5B%22con%22%5D&sensorCharacteristicList=%5B%7B%22characteristicName%22%3A%22pi%22%2C%22characteristicUnitOfMeasure%22%3A%22ae_parent%22%7D%5D&geographicalArea=%5B%7B%22shape%22%3A0%2C%22radius%22%3A6%2C%22points%22%3A%5B%7B%22latitude%22%3A0.8008281904610115%2C%22longitude%22%3A6.027456183070403%7D%2C%7B%22latitude%22%3A0.8008281904610115%2C%22longitude%22%3A6.027456183070403%7D%5D%7D%5D' \ + s, err := sbi.SensorDiscoveryInfoAll() if err != nil { errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) @@ -634,7 +724,7 @@ func sensorDiscoveryLookupGET(w http.ResponseWriter, r *http.Request) { // Prepare response and send jsonResponse := convertSensorDiscoveryInfoListToJson(sensors) if jsonResponse == "" { - log.Error("Marshalling failure") + log.Error("sensorDiscoveryLookupGET: Marshalling failure") errHandlerProblemDetails(w, "Marshalling failure", http.StatusInternalServerError) return } @@ -656,12 +746,20 @@ func sensorStatusLookupGET(w http.ResponseWriter, r *http.Request) { err := errors.New("Invalid query parameters") errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return + } else if len(q["sensorIdentifier"][0]) == 0 { + err := errors.New("Invalid query parameters") + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return } log.Debug("sensorStatusLookupGET: q[sensorIdentifier][0]: ", q["sensorIdentifier"][0]) s, err := sbi.GetSensorStatus(q["sensorIdentifier"][0]) if err != nil { - errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + if err.Error() == "Wrong Device identifier" { + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + } else { + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + } return } log.Debug("sensorStatusLookupGET: s: ", s) @@ -669,7 +767,7 @@ func sensorStatusLookupGET(w http.ResponseWriter, r *http.Request) { // Prepare response and send jsonResponse := convertSensorStatusToJson(convertSensorStatusInfoFromSbi(s)) if jsonResponse == "" { - log.Error("Marshalling failure") + log.Error("sensorDiscoveryLookupGET: Marshalling failure") errHandlerProblemDetails(w, "Marshalling failure", http.StatusInternalServerError) return } @@ -678,6 +776,49 @@ func sensorStatusLookupGET(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +func sensorDataLookupGET(w http.ResponseWriter, r *http.Request) { + log.Debug(">>> sensorDataLookupGET: ", r) + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + // Validate query parameters + u, _ := url.Parse(r.URL.String()) + q := u.Query() + log.Debug("sensorDataLookupGET: q: ", q) + if _, ok := q["sensorIdentifier"]; !ok { + err := errors.New("Invalid query parameters") + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } else if len(q["sensorIdentifier"][0]) == 0 { + err := errors.New("Invalid query parameters") + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + log.Debug("sensorDataLookupGET: q[sensorIdentifier][0]: ", q["sensorIdentifier"][0]) + + s, err := sbi.GetSensorData(q["sensorIdentifier"][0]) + if err != nil { + if err.Error() == "Wrong Device identifier" { + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + } else { + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + } + return + } + log.Debug("sensorDataLookupGET: s: ", s) + + // Prepare response and send + jsonResponse := convertSensorDataToJson(convertSensorDataFromSbi(s)) + if jsonResponse == "" { + log.Error("sensorDataLookupGET: Marshalling failure") + errHandlerProblemDetails(w, "Marshalling failure", http.StatusInternalServerError) + return + } + log.Info("sensorDataLookupGET: jsonResponse: ", jsonResponse) + fmt.Fprint(w, jsonResponse) + w.WriteHeader(http.StatusOK) +} + func sensorDiscoverySubscriptionGET(w http.ResponseWriter, r *http.Request) { log.Debug(">>> sensorDiscoverySubscriptionGET: ", r) @@ -695,6 +836,7 @@ func sensorDataSubscriptionGET(w http.ResponseWriter, r *http.Request) { subscriptionsGET(SENS_DATA, w, r) } + func sensorDiscoverySubscriptionPOST(w http.ResponseWriter, r *http.Request) { log.Debug(">>> sensorDiscoverySubscriptionPOST: ", r) @@ -743,17 +885,94 @@ func subscriptionsPOST(subType string, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") var subscriptionCommon SubscriptionCommon - // Read JSON input stream provided in the Request, and stores it in the bodyBytes as bytes - bodyBytes, _ := ioutil.ReadAll(r.Body) - log.Info("subscriptionsPost: bodyBytes: ", string(bodyBytes)) - // Unmarshal function to converts a JSON-formatted string into a SubscriptionCommon struct and store it in extractSubType - err := json.Unmarshal(bodyBytes, &subscriptionCommon) - if err != nil { - log.Error(err.Error()) - errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) - return + var bodyBytes []byte + var err error + switch subType { + case SENS_DISCOVERY: + var sub SensorDiscoverySubscriptionIdBody + // Read JSON input stream provided in the Request, and stores it in the bodyBytes as bytes + bodyBytes, err = ioutil.ReadAll(r.Body) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("subscriptionsPost: bodyBytes: ", string(bodyBytes)) + // Unmarshal function to converts a JSON-formatted string into a SubscriptionCommon struct and store it in extractSubType + err = json.Unmarshal(bodyBytes, &sub) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("subscriptionsPost: sub: ", sub) + subscriptionType := "" + if sub.SensorDiscoveryEventSubscription.SubscriptionType != nil { + subscriptionType = string(*sub.SensorDiscoveryEventSubscription.SubscriptionType) + } + subscriptionCommon = SubscriptionCommon{ + SubscriptionType: subscriptionType, + CallbackReference: sub.SensorDiscoveryEventSubscription.CallbackReference, + WebsockNotifConfig: sub.SensorDiscoveryEventSubscription.WebsockNotifConfig, + RequestTestNotification: sub.SensorDiscoveryEventSubscription.RequestTestNotification, + } + case SENS_STATUS: + var sub SensorStatusSubscriptionIdBody + // Read JSON input stream provided in the Request, and stores it in the bodyBytes as bytes + bodyBytes, err = ioutil.ReadAll(r.Body) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("subscriptionsPost: bodyBytes: ", string(bodyBytes)) + // Unmarshal function to converts a JSON-formatted string into a SubscriptionCommon struct and store it in extractSubType + err = json.Unmarshal(bodyBytes, &sub) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("subscriptionsPost: sub: ", sub) + subscriptionType := "" + if sub.SensorStatusSubscription.SubscriptionType != nil { + subscriptionType = string(*sub.SensorStatusSubscription.SubscriptionType) + } + subscriptionCommon = SubscriptionCommon{ + SubscriptionType: subscriptionType, + CallbackReference: sub.SensorStatusSubscription.CallbackReference, + WebsockNotifConfig: sub.SensorStatusSubscription.WebsockNotifConfig, + RequestTestNotification: sub.SensorStatusSubscription.RequestTestNotification, + } + case SENS_DATA: + var sub SensorDataSubscriptionIdBody + // Read JSON input stream provided in the Request, and stores it in the bodyBytes as bytes + bodyBytes, err = ioutil.ReadAll(r.Body) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("subscriptionsPost: bodyBytes: ", string(bodyBytes)) + // Unmarshal function to converts a JSON-formatted string into a SubscriptionCommon struct and store it in extractSubType + err = json.Unmarshal(bodyBytes, &sub) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("subscriptionsPost: sub: ", sub) + subscriptionType := "" + if sub.SensorDataSubscription.SubscriptionType != nil { + subscriptionType = string(*sub.SensorDataSubscription.SubscriptionType) + } + subscriptionCommon = SubscriptionCommon{ + SubscriptionType: subscriptionType, + CallbackReference: sub.SensorDataSubscription.CallbackReference, + WebsockNotifConfig: sub.SensorDataSubscription.WebsockNotifConfig, + RequestTestNotification: sub.SensorDataSubscription.RequestTestNotification, + } } - log.Info("subscriptionsPost: subscriptionCommon: ", subscriptionCommon) // Validating mandatory parameters provided in the request body if subscriptionCommon.SubscriptionType == "" { @@ -785,13 +1004,13 @@ func subscriptionsPOST(subType string, w http.ResponseWriter, r *http.Request) { // create a unique link for every subscription and concatenate subscription to it link := new(SubscriptionLinks) self := new(LinkType) - self.Href = hostUrl.String() + basePath + "subscriptions/" + subsIdStr - link.Self = self // switch statement is based on provided subscriptionType in the request body var jsonResponse string switch subscriptionType { case SENS_DISCOVERY: + self.Href = hostUrl.String() + basePath + "subscriptions/sensor_discovery/" + subsIdStr + link.Self = self var sensorDiscoveryEventSubscription SensorDiscoveryEventSubscription jsonResponse, err = processSensorDiscoveryEventSubscription(bodyBytes, link, subsIdStr, &sensorDiscoveryEventSubscription) if err != nil { @@ -802,6 +1021,8 @@ func subscriptionsPOST(subType string, w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", sensorDiscoveryEventSubscription.Links.Self.Href) case SENS_STATUS: + self.Href = hostUrl.String() + basePath + "subscriptions/sensor_status/" + subsIdStr + link.Self = self var sensorStatusSubscription SensorStatusSubscription jsonResponse, err = processSensorStatusSubscription(bodyBytes, link, subsIdStr, &sensorStatusSubscription) if err != nil { @@ -812,6 +1033,8 @@ func subscriptionsPOST(subType string, w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", sensorStatusSubscription.Links.Self.Href) case SENS_DATA: + self.Href = hostUrl.String() + basePath + "subscriptions/sensor_data/" + subsIdStr + link.Self = self var sensorDataSubscription SensorDataSubscription jsonResponse, err = processSensorDataSubscription(bodyBytes, link, subsIdStr, &sensorDataSubscription) if err != nil { @@ -858,13 +1081,27 @@ func subscriptionsGET(subType string, w http.ResponseWriter, r *http.Request) { u, _ := url.Parse(r.URL.String()) log.Info("url: ", u.RequestURI()) q := u.Query() - sensorIdentifier := q.Get("sensorIdentifier") - log.Debug("subscriptionsGET: sensorIdentifier: ", sensorIdentifier) - sensorIdentifiers := strings.Split(sensorIdentifier, ",") + sensorIdentifiers := []string{} + if len(q) != 0 { + sensorIdentifier := q.Get("sensorIdentifier") + if len(sensorIdentifier) == 0 { + err := errors.New("Wrong query parameters") + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + log.Debug("subscriptionsGET: sensorIdentifier: ", sensorIdentifier) + sensorIdentifiers = strings.Split(sensorIdentifier, ",") + } log.Debug("subscriptionsGET: sensorIdentifiers: ", sensorIdentifiers) // get the response against particular subscription type - response := createSubscriptionLinkList(subType, sensorIdentifiers) + response, err := createSubscriptionLinkList(subType, sensorIdentifiers) + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } // prepare & send response jsonResponse, err := json.Marshal(response) @@ -898,12 +1135,6 @@ func subscriptionsByIdGET(subType string, w http.ResponseWriter, r *http.Request subsIdStr := vars["subscriptionId"] log.Info("subsIdStr: ", subsIdStr) - q := u.Query() - sensorIdentifier := q.Get("sensorIdentifier") - log.Debug("subscriptionsByIdGET: sensorIdentifier: ", sensorIdentifier) - sensorIdentifiers := strings.Split(sensorIdentifier, ",") - log.Debug("subscriptionsByIdGET: sensorIdentifiers: ", sensorIdentifiers) - // Find subscription entry in Redis DB keyName := baseKey + "subscriptions:" + subsIdStr log.Info("subscriptionsByIdGET: keyName: ", keyName) @@ -1057,24 +1288,10 @@ func processSensorDiscoveryEventSubscription(bodyBytes []byte, link *Subscriptio // FIXME FSCOM Check filter values - if sensorDiscoveryEventSubscription.WebsockNotifConfig == nil && sensorDiscoveryEventSubscription.CallbackReference == "" { - err = errors.New("Mandatory CallbackReference parameter should be present") - log.Error(err.Error()) - return "", err - } - if sensorDiscoveryEventSubscription.CallbackReference == "" { - err = errors.New("CallbackReference parameter should be present") - log.Error(err.Error()) - return "", err - } - if sensorDiscoveryEventSubscription.WebsockNotifConfig != nil { - err = errors.New("WebsockNotifConfig not supported") - log.Error(err.Error()) - return "", err - } - registerSensorDiscoveryEventSubscription(subsIdStr, nil, sensorDiscoveryEventSubscription) + sensorDiscoveryEventSubscription.SubscriptionType = new(SubscriptionType) + *sensorDiscoveryEventSubscription.SubscriptionType = SENSOR_DISCOVERY_EVENT_SUBSCRIPTION sensorDiscoveryEventSubscription.Links = link // Store subscription key in redis @@ -1153,6 +1370,17 @@ func registerSensorDiscoveryEventSubscription(subId string, currentSensorDiscove intList = append(intList, subsId) subscriptionExpiryMap[int(sensorDiscoveryEventSubscription.ExpiryDeadline.Seconds)] = intList } + sub := sbi.SensorDiscoveryEventSubscription{ + SubscriptionType: string(SENSOR_DISCOVERY_EVENT_SUBSCRIPTION), + SensorInfoList: sensorDiscoveryEventSubscription.SensorInfoList, + } + // TODO FSCOM Add GeographicalArea field & create a convert function + err := sbi.ProcessSensorDiscoveryEventSubscription(sub) + if err != nil { + log.Error("Failed to register sensorDiscoveryEventSubscription: ", err) + errHandlerProblemDetails(nil, err.Error(), http.StatusInternalServerError) + return + } } log.Info("registerSensorDiscoveryEventSubscription: After subscriptionExpiryMap: ", subscriptionExpiryMap) log.Info("New registration: ", subsId, " type: ", SENS_DISCOVERY) @@ -1206,24 +1434,10 @@ func processSensorStatusSubscription(bodyBytes []byte, link *SubscriptionLinks, // FIXME FSCOM Check filter values - if sensorStatusSubscription.WebsockNotifConfig == nil && sensorStatusSubscription.CallbackReference == "" { - err = errors.New("Mandatory CallbackReference parameter should be present") - log.Error(err.Error()) - return "", err - } - if sensorStatusSubscription.WebsockNotifConfig != nil { - err = errors.New("WebsockNotifConfig not supported") - log.Error(err.Error()) - return "", err - } - if sensorStatusSubscription.CallbackReference == "" { - err = errors.New("CallbackReference parameter should be present") - log.Error(err.Error()) - return "", err - } - registerSensorStatusSubscription(subsIdStr, nil, sensorStatusSubscription) + sensorStatusSubscription.SubscriptionType = new(SubscriptionType) + *sensorStatusSubscription.SubscriptionType = SENSOR_STATUS_SUBSCRIPTION sensorStatusSubscription.Links = link // Store subscription key in redis @@ -1356,24 +1570,10 @@ func processSensorDataSubscription(bodyBytes []byte, link *SubscriptionLinks, su // FIXME FSCOM Check filter values - if sensorDataSubscription.WebsockNotifConfig == nil && sensorDataSubscription.CallbackReference == "" { - err = errors.New("Mandatory CallbackReference parameter should be present") - log.Error(err.Error()) - return "", err - } - if sensorDataSubscription.WebsockNotifConfig != nil { - err = errors.New("WebsockNotifConfig not supported") - log.Error(err.Error()) - return "", err - } - if sensorDataSubscription.CallbackReference == "" { - err = errors.New("CallbackReference parameter should be present") - log.Error(err.Error()) - return "", err - } - registerSensorDataSubscription(subsIdStr, nil, sensorDataSubscription) + sensorDataSubscription.SubscriptionType = new(SubscriptionType) + *sensorDataSubscription.SubscriptionType = SENSOR_DATA_SUBSCRIPTION sensorDataSubscription.Links = link // Store subscription key in redis @@ -1476,7 +1676,7 @@ func isSubscriptionIdRegisteredSensorData(subsIdStr string) bool { return returnVal } -func createSubscriptionLinkList(subType string, sensorIdentifiers []string) *SubscriptionLinkList { +func createSubscriptionLinkList(subType string, sensorIdentifiers []string) (*SubscriptionLinkList, error) { log.Debug(">>> createSubscriptionLinkList: subType: ", subType) log.Debug(">>> createSubscriptionLinkList: sensorIdentifiers: ", sensorIdentifiers) @@ -1553,7 +1753,7 @@ func createSubscriptionLinkList(subType string, sensorIdentifiers []string) *Sub subscriptionLinkList.Links = links - return subscriptionLinkList + return subscriptionLinkList, nil } /* @@ -1794,49 +1994,87 @@ func dataNotify(map[string]interface{}) { func convertSensorDiscoveryInfoListFromSbi_with_filter(s []sbi.SensorDiscoveryInfo, filter url.Values) (sensors []SensorDiscoveryInfo) { log.Debug(">>> convertSensorDiscoveryInfoListFromSbi_with_filter: s: ", s) log.Debug(">>> convertSensorDiscoveryInfoListFromSbi_with_filter: filter: ", filter) + //log.Debug("sensorDiscoveryLookupGET: q[sensorCharacteristicList][0]: ", q["sensorCharacteristicList"][0]) + //log.Debug("sensorDiscoveryLookupGET: type(q[sensorCharacteristicList][0]): ", reflect.TypeOf(q["sensorCharacteristicList"][0])) + //q: map[geographicalArea:[[object Object]] sensorCharacteristicList:[[object Object]] sensorPropertyList:[string1,string2] type:[string]]","time":"2025-02-04T08:35:35Z"} + // /sens/v1/queries/sensor_discovery?type=4&sensorPropertyList=%5B%22con%22%5D&sensorCharacteristicList=%5B%7B%22characteristicName%22%3A%22pi%22%2C%22characteristicUnitOfMeasure%22%3A%22ae_parent%22%7D%5D&geographicalArea=%5B%7B%22shape%22%3A0%2C%22radius%22%3A6%2C%22points%22%3A%5B%7B%22latitude%22%3A0.8008281904610115%2C%22longitude%22%3A6.027456183070403%7D%2C%7B%22latitude%22%3A0.8008281904610115%2C%22longitude%22%3A6.027456183070403%7D%5D%7D%5D' \ sensors = make([]SensorDiscoveryInfo, 0) for _, val := range s { - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing val: ", val.SensorPropertyList) + log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing val: ", val) if filter["type"][0] != val.SensorType { log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: ", filter["type"][0], " not found, discard it") continue // Discard it } else { - if len(val.SensorPropertyList) != 0 { - if f, ok := filter["sensorPropertyList"]; ok { - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: f: ", f) + // Apply filter on sensorPropertyList + if filter["sensorPropertyList"][0] != "saref:any" { // FIXME FSCOM Which values to be used??? + if len(val.SensorPropertyList) == 0 { + log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: ", filter["sensorPropertyList"][0], " not found, discard it") + continue // Discard it + } else { found := 0 - for _, fu := range f { - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing fu: ", fu) + for _, fu := range filter["sensorPropertyList"] { + log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter (sensorPropertyList): processing fu: ", fu) l := strings.Split(fu, ",") log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: l: ", l) - for _, s := range l { - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing s: ", s) - for _, v := range val.SensorPropertyList { - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing v: ", v) - if v == s { - found += 1 - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: matching: ", found) - break // Exit loop on sensor SensorPropertyList - } - } // End of 'for' statement - } // End of 'for' statement - log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: found: ", found, " compared to ", len(l)) - if found == len(l) { // All filter iems found - sensors = append(sensors, convertSensorDiscoveryInfoFromSbi(val)) - } else { - break // Exit loop on filter list - } + // TODO FSCOM Apply filter on } // End of 'for' statement - // FIXME FSCOM Add filtering - } else { - sensors = append(sensors, convertSensorDiscoveryInfoFromSbi(val)) + if found != len(val.SensorPropertyList) { // Not all filter items found + log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: Not all filter items found, discard it") + continue // Discard it + } } - } else { + } // else, continue + + // Apply filter on sensorCharacteristicList + if _, ok := filter["sensorCharacteristicList"]; !ok { // 'sensorCharacteristicList'filter omitted sensors = append(sensors, convertSensorDiscoveryInfoFromSbi(val)) + } else { + found := 0 + for _, fu := range filter["sensorCharacteristicList"] { + log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter (sensorCharacteristicList): processing fu: ", fu) + l := strings.Split(fu, ",") + log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: l: ", l) + // TODO FSCOM Apply filter on + } // End of 'for' statement + if found == len(filter["sensorCharacteristicList"]) { // All filter iems found + sensors = append(sensors, convertSensorDiscoveryInfoFromSbi(val)) + } else { + continue // Skip this item + } + + // if len(val.SensorPropertyList) != 0 { + // if f, ok := filter["sensorPropertyList"]; ok { + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: f: ", f) + // found := 0 + // for _, fu := range f { + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing fu: ", fu) + // l := strings.Split(fu, ",") + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: l: ", l) + // for _, s := range l { + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing s: ", s) + // for _, v := range val.SensorPropertyList { + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: processing v: ", v) + // if v == s { + // found += 1 + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: matching: ", found) + // break // Exit loop on sensor SensorPropertyList + // } + // } // End of 'for' statement + // } // End of 'for' statement + // log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: found: ", found, " compared to ", len(l)) + // if found == len(l) { // All filter iems found + // sensors = append(sensors, convertSensorDiscoveryInfoFromSbi(val)) + // } else { + // break // Exit loop on filter list + // } + // } // End of 'for' statement + // // FIXME FSCOM Add filtering + // } else { + // sensors = append(sensors, convertSensorDiscoveryInfoFromSbi(val)) + // } } } - } // End of 'for' statement log.Debug("convertSensorDiscoveryInfoListFromSbi_with_filter: sensors: ", sensors) @@ -1845,8 +2083,9 @@ func convertSensorDiscoveryInfoListFromSbi_with_filter(s []sbi.SensorDiscoveryIn func convertSensorDiscoveryInfoFromSbi(s sbi.SensorDiscoveryInfo) (sensor SensorDiscoveryInfo) { sensor = SensorDiscoveryInfo{ - SensorIdentifier: s.SensorIdentifier, - SensorType: s.SensorType, + SensorIdentifier: s.SensorIdentifier, + SensorType: s.SensorType, + SensorPropertyList: []string{"SAREF"}, } if len(s.SensorPropertyList) != 0 { copy(sensor.SensorPropertyList, s.SensorPropertyList) @@ -1866,11 +2105,33 @@ func convertSensorDiscoveryInfoFromSbi(s sbi.SensorDiscoveryInfo) (sensor Sensor Latitude: s.SensorPosition.Latitude, Longitude: s.SensorPosition.Longitude, } + } else { // SensorPosition is mandatory + sensor.SensorPosition = &Point{ + Latitude: 0, + Longitude: 0, + } } return sensor } +func convertSensorDataFromSbi(d sbi.SensorData) (data SensorData) { + data = SensorData{ + SensorIdentifier: d.SensorIdentifier, + Data: d.Data, + DataFormat: "", + DataUnitOfMeasure: "", + } + if d.DataTimestamp != nil { + data.DataTimestamp = &TimeStamp{ + Seconds: d.DataTimestamp.Seconds, + NanoSeconds: d.DataTimestamp.NanoSeconds, + } + } + + return data +} + func convertSensorStatusInfoFromSbi(s sbi.SensorStatusInfo) (status SensorStatusInfo) { status = SensorStatusInfo{ SensorIdentifier: s.SensorIdentifier, diff --git a/go-apps/meep-sss/server/model_status_data_subscription_id_body.go b/go-apps/meep-sss/server/model_status_data_subscription_id_body.go index f6b6594b6a0a485869e7d20725293c33ad90228c..6320ece9a1e2f537fe496d93d6e9f9843037e9c2 100644 --- a/go-apps/meep-sss/server/model_status_data_subscription_id_body.go +++ b/go-apps/meep-sss/server/model_status_data_subscription_id_body.go @@ -9,6 +9,6 @@ */ package server -type StatusDataSubscriptionIdBody struct { - SensorStatusSubscription *SensorDataSubscription `json:"SensorStatusSubscription,omitempty"` +type SensorDataSubscriptionIdBody struct { + SensorDataSubscription *SensorDataSubscription `json:"SensorStatusSubscription,omitempty"` } diff --git a/go-apps/meep-sss/server/routers.go b/go-apps/meep-sss/server/routers.go index 35b06a0dbc8d6ef324419a306f9265eb16458575..a8d164cf5fb93afd80cd01a532b78ebd3b1134f7 100644 --- a/go-apps/meep-sss/server/routers.go +++ b/go-apps/meep-sss/server/routers.go @@ -83,21 +83,21 @@ var routes = Routes{ Route{ "SensorDataLookupGET", strings.ToUpper("Get"), - "/sens/v1/queries/status_data", + "/sens/v1/queries/sensor_data", SensorDataLookupGET, }, Route{ "SensorDataIndividualSubscriptionGET", strings.ToUpper("Get"), - "/sens/v1/queries/status_data/{subscriptionId}", + "/sens/v1/queries/sensor_data/{subscriptionId}", SensorDataIndividualSubscriptionGET, }, Route{ "SensorDataSubscriptionDELETE", strings.ToUpper("Delete"), - "/sens/v1/queries/status_data/{subscriptionId}", + "/sens/v1/queries/sensor_data/{subscriptionId}", SensorDataSubscriptionDELETE, }, @@ -118,7 +118,7 @@ var routes = Routes{ Route{ "SensorDataSubscriptionPUT", strings.ToUpper("Put"), - "/sens/v1/queries/status_data/{subscriptionId}", + "/sens/v1/queries/sensor_data/{subscriptionId}", SensorDataSubscriptionPUT, }, diff --git a/go-apps/meep-sss/server/subscriptionCommon.go b/go-apps/meep-sss/server/subscriptionCommon.go index 059567bf6d66933fbe1f97fece3c90eafa9459b3..e07c493bba55e230c127222b091f5d4a9492bca1 100644 --- a/go-apps/meep-sss/server/subscriptionCommon.go +++ b/go-apps/meep-sss/server/subscriptionCommon.go @@ -27,8 +27,8 @@ type SubscriptionCommon struct { Links *SubscriptionLinks `json:"_links,omitempty"` // URI selected by the service consumer, to receive notifications on the subscribed RNIS information. This shall be included in the request and response. SubscriptionType string `json:"subscriptionType"` - RequestTestNotification bool `json:"requestTestNotification"` - CallbackReference string `json:"callbackReference"` + RequestTestNotification bool `json:"requestTestNotification,omitempty"` + CallbackReference string `json:"callbackReference,omitempty"` WebsockNotifConfig *WebsockNotifConfig `json:"websockNotifConfig,omitempty"` ExpiryDeadline *TimeStamp `json:"expiryDeadline,omitempty"` } diff --git a/go-apps/meep-virt-engine/entrypoint.sh b/go-apps/meep-virt-engine/entrypoint.sh index 899913f72a76f1f9bb6b3bd06d9deb1b9f17ec03..06c9628fda0c6a2cfd3aa121f23a8b191c33fd0c 100755 --- a/go-apps/meep-virt-engine/entrypoint.sh +++ b/go-apps/meep-virt-engine/entrypoint.sh @@ -22,8 +22,14 @@ mv /meep-tc-engine /templates/sandbox/meep-tc-engine mv /meep-vis /templates/sandbox/meep-vis mv /meep-dai /templates/sandbox/meep-dai mv /meep-tm /templates/sandbox/meep-tm +mv /meep-iot /templates/sandbox/meep-iot +mv /meep-sss /templates/sandbox/meep-sss mv /meep-mosquitto /templates/sandbox/meep-mosquitto mv /meep-cloud-mosquitto /templates/sandbox/meep-cloud-mosquitto +mv /meep-acme-in-cse /templates/sandbox/meep-acme-in-cse +mv /meep-acme-mn-cse /templates/sandbox/meep-acme-mn-cse +mv /meep-tinyiot-in-cse /templates/sandbox/meep-tinyiot-in-cse +mv /meep-tinyiot-mn-cse /templates/sandbox/meep-tinyiot-mn-cse mkdir -p /templates/scenario diff --git a/go-apps/meep-virt-engine/go.mod b/go-apps/meep-virt-engine/go.mod index e80bab8085c2f5bf1bc07a30ecdb4de07a40452b..88c118c8771a07900990da8ddd83f8633488554c 100644 --- a/go-apps/meep-virt-engine/go.mod +++ b/go-apps/meep-virt-engine/go.mod @@ -8,6 +8,7 @@ require ( github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-model v0.0.0 github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-mq v0.0.0 github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-watchdog v0.0.0 + github.com/google/uuid v1.2.0 // indirect ) replace ( diff --git a/go-apps/meep-virt-engine/server/chart-template.go b/go-apps/meep-virt-engine/server/chart-template.go index 7681a997098647f13d48f945809733cbc0f0ba81..a8763b5db9af6fcc29bd5887969e07f470f8a617 100644 --- a/go-apps/meep-virt-engine/server/chart-template.go +++ b/go-apps/meep-virt-engine/server/chart-template.go @@ -182,29 +182,22 @@ func getAppEnablement(mepName string, model *mod.Model) string { func generateScenarioCharts(sandboxName string, procName string, model *mod.Model) (charts []helm.Chart, err error) { serviceMap := map[string]string{} - procNames := model.GetNodeNames("CLOUD-APP") - procNames = append(procNames, model.GetNodeNames("EDGE-APP")...) - procNames = append(procNames, model.GetNodeNames("UE-APP")...) - for _, name := range procNames { + processes := model.GetProcesses(nil) + for _, proc := range processes.Processes { // Check if single process is being added - if procName != "" && name != procName { + if procName != "" && proc.Name != procName { continue } // Retrieve node process information from model - node := model.GetNode(name) + node := model.GetNodeById(proc.Id) if node == nil { - err = errors.New("Error finding process: " + name) - return nil, err - } - proc, ok := node.(*dataModel.Process) - if !ok { - err = errors.New("Error casting process: " + name) + err = errors.New("Error finding process: " + proc.Name + " with ID: " + proc.Id) return nil, err } - ctx := model.GetNodeContext(name) + ctx := model.GetNodeContextById(proc.Id) if ctx == nil { - err = errors.New("Error finding context for process: " + name) + err = errors.New("Error finding context for process: " + proc.Name + " with ID: " + proc.Id) return nil, err } @@ -256,12 +249,13 @@ func generateScenarioCharts(sandboxName string, procName string, model *mod.Mode } } } - } else if mepService := getMepService(proc); mepService != "" { + } else if mepService := getMepService(&proc); mepService != "" { log.Debug("Processing MEP Service chart for element[", proc.Name, "]") // Get MEP Name mepName := ctx.Parents[mod.PhyLoc] + // Important part of code. // Create Sandbox template var sandboxTemplate SandboxTemplate sandboxTemplate.InstanceId = proc.Id @@ -291,7 +285,10 @@ func generateScenarioCharts(sandboxName string, procName string, model *mod.Mode } // Create chart - chartName := proc.Name + chartName := mepName + "-" + proc.Name + if len(chartName) > 53 { + chartName = chartName[:53] + } chartLocation, _, err := createChart(chartName, sandboxName, scenarioName, mepService, sandboxTemplate) if err != nil { log.Debug("yaml creation file process: ", err) @@ -572,6 +569,11 @@ func newChart(chartName string, sandboxName string, scenarioName string, chartLo chart.ReleaseName = "meep-" + scenarioName + "-" + chartName } + // Truncate release name to 53 characters to comply with Helm requirements + if len(chart.ReleaseName) > 53 { + chart.ReleaseName = chart.ReleaseName[:53] + } + chart.Name = chartName chart.Namespace = sandboxName chart.Location = chartLocation diff --git a/go-apps/meepctl/cmd/version.go b/go-apps/meepctl/cmd/version.go index ecd5804691394dfc1cb1806e675a80760567ab55..0c5d4355c72bd1704e004e5cf378f418e0b145d8 100644 --- a/go-apps/meepctl/cmd/version.go +++ b/go-apps/meepctl/cmd/version.go @@ -41,7 +41,7 @@ type versionInfo struct { BuildID string `json:"build,omitempty"` } -const meepctlVersion = "1.10.0" +const meepctlVersion = "1.11.0" const na = "NA" const versionDesc = `Display version information diff --git a/go-apps/meepctl/utils/config.go b/go-apps/meepctl/utils/config.go index 1bbe4e5df607975014791288d0f2851a67d7bb62..198b790ebe68375be23228c29f0b3aed9d1714a2 100644 --- a/go-apps/meepctl/utils/config.go +++ b/go-apps/meepctl/utils/config.go @@ -31,7 +31,7 @@ import ( yaml "gopkg.in/yaml.v2" ) -const configVersion = "1.10.0" +const configVersion = "1.11.0" const defaultNotSet = "not set" diff --git a/go-packages/meep-federation-client/go.sum b/go-packages/meep-federation-client/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..f04cf1014d31a52f83095e073b4397bf9cd94713 --- /dev/null +++ b/go-packages/meep-federation-client/go.sum @@ -0,0 +1,15 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/go-packages/meep-iot-client/README.md b/go-packages/meep-iot-client/README.md index b7c10a4112b6f85fb27010d77121ba44bf74257c..83e66044fc625c6e02564b0576b1401f0a77272f 100644 --- a/go-packages/meep-iot-client/README.md +++ b/go-packages/meep-iot-client/README.md @@ -18,7 +18,7 @@ import "./swagger" ## Documentation for API Endpoints -All URIs are relative to *https://localhost/sandboxname/sandboxname/amsi/v1* +All URIs are relative to *https://localhost/sandboxname/iots/v1* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- diff --git a/go-packages/meep-iot-client/configuration.go b/go-packages/meep-iot-client/configuration.go index b2852ee53df3856c8370b2bf3494cb7cc6bc49d5..5f393e3c2d3512642f45ac0f5d69f767051d61b1 100644 --- a/go-packages/meep-iot-client/configuration.go +++ b/go-packages/meep-iot-client/configuration.go @@ -60,7 +60,7 @@ type Configuration struct { func NewConfiguration() *Configuration { cfg := &Configuration{ - BasePath: "https://localhost/sandboxname/sandboxname/amsi/v1", + BasePath: "https://localhost/sandboxname/iots/v1", DefaultHeader: make(map[string]string), UserAgent: "Swagger-Codegen/1.0.0/go", } diff --git a/go-packages/meep-iot-client/docs/RegDevApi.md b/go-packages/meep-iot-client/docs/RegDevApi.md index 6927235585df40767313da72af2385176f36bafb..08da2c2506d5ef6468da9b7c39736303befa4e93 100644 --- a/go-packages/meep-iot-client/docs/RegDevApi.md +++ b/go-packages/meep-iot-client/docs/RegDevApi.md @@ -1,6 +1,6 @@ # {{classname}} -All URIs are relative to *https://localhost/sandboxname/sandboxname/amsi/v1* +All URIs are relative to *https://localhost/sandboxname/iots/v1* Method | HTTP request | Description ------------- | ------------- | ------------- diff --git a/go-packages/meep-iot-client/docs/RegIotPlatApi.md b/go-packages/meep-iot-client/docs/RegIotPlatApi.md index 3041d85f50b2c75458a2e2b7eb598263178aa413..83062531a92c76668ceae3ae90db693025a4bead 100644 --- a/go-packages/meep-iot-client/docs/RegIotPlatApi.md +++ b/go-packages/meep-iot-client/docs/RegIotPlatApi.md @@ -1,6 +1,6 @@ # {{classname}} -All URIs are relative to *https://localhost/sandboxname/sandboxname/amsi/v1* +All URIs are relative to *https://localhost/sandboxname/iots/v1* Method | HTTP request | Description ------------- | ------------- | ------------- diff --git a/go-packages/meep-iot-mgr/go.mod b/go-packages/meep-iot-mgr/go.mod index d9b09212a86a776b391f2f2ab8bc2a2bbe6f034f..c082d159a3aaa746ec7de005658eb881edd0cbe0 100644 --- a/go-packages/meep-iot-mgr/go.mod +++ b/go-packages/meep-iot-mgr/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/BurntSushi/toml v1.2.0 // indirect + github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sss-mgr v0.0.0 github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger v0.0.0 github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/google/uuid v1.6.0 @@ -16,4 +17,5 @@ require ( replace ( github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger => ../../go-packages/meep-logger + github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sss-mgr => ../../go-packages/meep-sss-mgr ) diff --git a/go-packages/meep-iot-mgr/iot-mgr.go b/go-packages/meep-iot-mgr/iot-mgr.go index a517df845209a71ebd8e6be5a984b07d60aa7318..336b40450c553336d73648e9f140e079f80127aa 100644 --- a/go-packages/meep-iot-mgr/iot-mgr.go +++ b/go-packages/meep-iot-mgr/iot-mgr.go @@ -30,7 +30,6 @@ type IotMgr struct { name string namespace string mutex sync.Mutex - wg sync.WaitGroup refreshTicker *time.Ticker } @@ -105,12 +104,19 @@ type TrafficRuleDescriptor struct { type InterfaceDescriptor struct { InterfaceType string - //TunnelInfo *TunnelInfo FSCOM Not supported + TunnelInfo *TunnelInfo SrcMACAddress string DstMACAddress string DstIPAddress string } +type TunnelInfo struct { + TunnelType string + TunnelDstAddress string + TunnelSrcAddress string + TunnelSpecificData string +} + type TrafficFilter struct { SrcAddress []string DstAddress []string @@ -125,6 +131,7 @@ type TrafficFilter struct { SrcTunnelPort []string DstTunnelPort []string QCI int32 + DSCP int32 TC int32 } @@ -133,30 +140,68 @@ type KeyValuePair struct { Value string } +type DownlinkInfo struct { + DownlinkTopic string + DevicePort int32 +} + +type DeviceSpecificMessageFormats struct { + EventMsgFormat *EventMsg + UplinkMsgFormat *UplinkMsg +} + +type EventMsg struct { + EventTopic string + SelectedSerializer *string + IncludeDeviceAddr bool + IncludeDeviceMetadata bool + IncludePei bool + IncludeSupi bool + IncludeImei bool + IncludeImsi bool + IncludeIccid bool + IncludeDeviceId bool +} + +type UplinkMsg struct { + UplinkTopic string + SelectedSerializer *string + IncludeDevicePort bool + IncludeDeviceAddr bool + IncludeDeviceMetadata bool + IncludePei bool + IncludeSupi bool + IncludeImei bool + IncludeImsi bool + IncludeIccid bool + IncludeDeviceId bool +} + type DeviceInfo struct { - DeviceAuthenticationInfo string - DeviceMetadata []KeyValuePair - Gpsi string - Pei string - Supi string - Msisdn string - Imei string - Imsi string - Iccid string - DeviceId string - RequestedMecTrafficRule []TrafficRuleDescriptor - RequestedIotPlatformId string - RequestedUserTransportId string - //DeviceSpecificMessageFormats *DeviceSpecificMessageFormats - //DownlinkInfo *DownlinkInfo - ClientCertificate string - Enabled bool -} - -var registeredIotPlatformsMap = map[string]IotPlatformInfo{} // List of discovered IOT Plateform -var devicesMap = map[string]DeviceInfo{} // Map device by deviceId -var devicesPerPlatformMap = map[string][]string{} // Map deviceIds per platform -var platformPerUserTransportIdMap = map[string][]string{} // Map userTransportId per platform + DeviceAuthenticationInfo string + DeviceMetadata []KeyValuePair + Gpsi string + Pei string + Supi string + Msisdn string + Imei string + Imsi string + Iccid string + DeviceId string + RequestedMecTrafficRule []TrafficRuleDescriptor + RequestedIotPlatformId string + RequestedUserTransportId string + DeviceSpecificMessageFormats *DeviceSpecificMessageFormats + DownlinkInfo *DownlinkInfo + ClientCertificate string + Enabled bool +} + +var registeredIotPlatformsMap = map[string]IotPlatformInfo{} // List of discovered IOT Plateform +var devicesMap = map[string]DeviceInfo{} // Map device by deviceId +var devicesPerPlatformMap = map[string][]string{} // Map deviceIds per platform +var platformPerUserTransportIdMap = map[string][]string{} // Map userTransportId per platform +var registeredIotPlatformsAEMap = map[string]sssmgr.SensorDiscoveryInfo{} // Map AE created per IOT Plateform // Timer to refresh devices list for all IoT platform const refreshTickerExpeary = 30 // In seconds @@ -202,39 +247,44 @@ func (tm *IotMgr) init() { devicesMap = make(map[string]DeviceInfo, 0) devicesPerPlatformMap = make(map[string][]string, 0) platformPerUserTransportIdMap = make(map[string][]string, 0) + registeredIotPlatformsAEMap = make(map[string]sssmgr.SensorDiscoveryInfo, 0) tm.refreshTicker = nil } // DeleteIotMgr - func (tm *IotMgr) DeleteIotMgr() (err error) { + registeredIotPlatformsMap = nil + devicesMap = nil + devicesPerPlatformMap = nil + platformPerUserTransportIdMap = nil + registeredIotPlatformsAEMap = nil + tm.refreshTicker = nil return nil } func (tm *IotMgr) startRefreshTicker() { - log.Debug(">>> startRefreshTicker") + log.Debug(">>> iotmgr.startRefreshTicker: ", time.Duration(refreshTickerExpeary)*time.Second) tm.refreshTicker = time.NewTicker(time.Duration(refreshTickerExpeary) * time.Second) go func() { if tm.refreshTicker != nil { for range tm.refreshTicker.C { - // Refresh the list of devices - tm.wg.Add(1) - log.Debug("startRefreshTicker: registeredIotPlatformsMap: ", registeredIotPlatformsMap) + log.Debug("iotmgr.startRefreshTicker: Fired") + log.Debug("iotmgr.startRefreshTicker: registeredIotPlatformsMap: ", registeredIotPlatformsMap) for _, v := range registeredIotPlatformsMap { if v.oneM2M != nil { + tm.mutex.Lock() err := tm.populateDevicesPerIotPlatforms(v) + tm.mutex.Unlock() if err != nil { log.Error(err) } } else { - log.Debug("startRefreshTicker: Nothing to do") + log.Debug("iotmgr.startRefreshTicker: Nothing to do") } } // End of 'for' statement - log.Debug("startRefreshTicker: Before Done()") - tm.wg.Done() - log.Debug("startRefreshTicker: After Done()") } // End of 'for' statement - log.Debug("startRefreshTicker: Leaving time loop") + log.Debug("iotmgr.startRefreshTicker: Leaving time loop") } }() } @@ -242,10 +292,8 @@ func (tm *IotMgr) startRefreshTicker() { func (tm *IotMgr) stopRefreshTicker() { if tm.refreshTicker != nil { // Refresh the list of devices - tm.wg.Add(1) tm.refreshTicker.Stop() tm.refreshTicker = nil - tm.wg.Done() log.Debug("Refresh loop stopped") } } @@ -256,28 +304,49 @@ func (tm *IotMgr) RegisterIotPlatformInfo(iotPlatformInfo IotPlatformInfo) (err } log.Info(">>> RegisterIotPlatformInfo: iotPlatformId: ", iotPlatformInfo) + + tm.mutex.Lock() + defer tm.mutex.Unlock() + if iotPlatformInfo.Enabled { - //{{\"iotPlatformId\": \"1a584db5-6a3e-4f56-b126-29180069ecf1\", \"userTransportInfo\": [{\"id\": \"ca22ca5e-e0ce-4da8-a2ce-2966f4759032\", \"name\": \"MQTT\", \"description\": \"MQTT\", \"type\": \"MB_TOPIC_BASED\", \"protocol\": \"MQTT\", \"version\": \"2\", \"endpoint\": {\"addresses\": [{\"host\": \"172.29.10.56\", \"port\": 1883}]}, \"security\": {}, \"implSpecificInfo\": {}}], \"customServicesTransportInfo\": [{\"id\": \"85fe5e7f-c371-4f71-b7f6-61a1f808fbb3\", \"name\": \"/laboai-acme-ic-cse\", \"description\": \"ACME oneM2M CSE\", \"type\": \"REST_HTTP\", \"protocol\": \"REST_HTTP\", \"version\": \"4\", \"endpoint\": {\"addresses\": [{\"host\": \"172.29.10.20\", \"port\": 31110}]}, \"security\": {}}], \"enabled\": true}} iotPlatformInfo.oneM2M = nil - if len(iotPlatformInfo.CustomServicesTransportInfo) == 0 || iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint == nil || len(iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint.Addresses) == 0 { + // if len(iotPlatformInfo.UserTransportInfo) == 0 || iotPlatformInfo.UserTransportInfo[0].Endpoint == nil || len(iotPlatformInfo.UserTransportInfo[0].Endpoint.Addresses) == 0 { + if len(iotPlatformInfo.CustomServicesTransportInfo) == 0 || iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint == nil || len(iotPlatformInfo.UserTransportInfo[0].Endpoint.Addresses) == 0 { log.Warn("RegisterIotPlatformInfo: Cannot use provided CustomServicesTransportInfo") } else { - // FIXME FSCOM How to get the CSE_ID + // FIXME FSCOM How to get the CSE_ID. For the time being, iotPlatformInfo.UserTransportInfo[0].Name is the CSE-resourceID // TODO FSCOM Add notification support? - pltf, err := sssmgr.NewSssMgr(tm.name, tm.namespace, iotPlatformInfo.CustomServicesTransportInfo[0].Protocol /*"MQTT"*/, iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint.Addresses[0].Host /*"172.29.10.56"*/, int(iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint.Addresses[0].Port) /*1883*/, iotPlatformInfo.IotPlatformId /*"7feaadbb0400"*/, iotPlatformInfo.CustomServicesTransportInfo[0].Name /*"laboai-acme-ic-cse"*/, nil, nil, nil) + pltf, err := sssmgr.NewSssMgr(tm.name, tm.namespace, iotPlatformInfo.CustomServicesTransportInfo[0].Protocol, iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint.Addresses[0].Host, int(iotPlatformInfo.CustomServicesTransportInfo[0].Endpoint.Addresses[0].Port), iotPlatformInfo.IotPlatformId, iotPlatformInfo.CustomServicesTransportInfo[0].Name, nil, nil, nil) if err != nil { log.Error("RegisterIotPlatformInfo: ", err) iotPlatformInfo.oneM2M = nil } else { log.Info("RegisterIotPlatformInfo: IoT pltf created") iotPlatformInfo.oneM2M = pltf - if tm.refreshTicker == nil { - log.Info("RegisterIotPlatformInfo: Start RefreshTicker") - tm.startRefreshTicker() + // Create the an AE for this ETSI MEC platform + var ae = sssmgr.SensorDiscoveryInfo{ + SensorIdentifier: iotPlatformInfo.IotPlatformId, + SensorType: "AE", + SensorPosition: nil, + IotPlatformId: iotPlatformInfo.IotPlatformId, + } + ae, err = pltf.OneM2M_create(ae, "") + if err != nil { + log.Error("RegisterIotPlatformInfo: Failed to create new AE sensor, plateform registration rejected") + pltf.DeleteSssMgr() + iotPlatformInfo.oneM2M = nil + return err + } else { + registeredIotPlatformsAEMap[iotPlatformInfo.IotPlatformId] = ae + if tm.refreshTicker == nil { + log.Info("RegisterIotPlatformInfo: Start RefreshTicker") + tm.startRefreshTicker() + } } } } registeredIotPlatformsMap[iotPlatformInfo.IotPlatformId] = iotPlatformInfo + log.Info("RegisterIotPlatformInfo: iotPlatformId: ", registeredIotPlatformsMap[iotPlatformInfo.IotPlatformId]) } // else, Skip disabled platform @@ -295,6 +364,10 @@ func (tm *IotMgr) DeregisterIotPlatformInfo(iotPlatformId string) (err error) { } log.Info(">>> DeregisterIotPlatformInfo: iotPlatformId: ", iotPlatformId) + + tm.mutex.Lock() + defer tm.mutex.Unlock() + // Remove the list of the devices for this IoT platform if val, ok := devicesPerPlatformMap[iotPlatformId]; ok { // Free resources from devicesMap map @@ -314,6 +387,10 @@ func (tm *IotMgr) DeregisterIotPlatformInfo(iotPlatformId string) (err error) { } if pltf, ok := registeredIotPlatformsMap[iotPlatformId]; ok { if pltf.oneM2M != nil { + if val, ok := registeredIotPlatformsAEMap[iotPlatformId]; ok { + _ = pltf.oneM2M.OneM2M_delete(val) + delete(registeredIotPlatformsAEMap, iotPlatformId) + } _ = pltf.oneM2M.DeleteSssMgr() pltf.oneM2M = nil log.Info("RegisterIotPlatformInfo: IoT pltf removed") @@ -343,15 +420,15 @@ func (tm *IotMgr) GetDevices() (devices []DeviceInfo, err error) { log.Info(">>> GetDevices") - tm.wg.Wait() - log.Info("GetDevices: After Wait()") + tm.mutex.Lock() + defer tm.mutex.Unlock() devices = make([]DeviceInfo, 0) if len(registeredIotPlatformsMap) == 0 { return devices, nil } - for _, v := range devicesMap { + for _, v := range devicesMap { // Process all devices log.Info("GetDevices: adding device: ", v) devices = append(devices, v) } // End of 'for' statement @@ -372,8 +449,8 @@ func (tm *IotMgr) GetDevice(deviceId string) (device DeviceInfo, err error) { log.Info(">>> GetDevice: deviceId: ", deviceId) - tm.wg.Wait() - log.Info("GetDevices: After Wait()") + tm.mutex.Lock() + defer tm.mutex.Unlock() if val, ok := devicesMap[deviceId]; !ok { err = errors.New("Wrong Device identifier") @@ -392,17 +469,15 @@ func (tm *IotMgr) GetDevice(deviceId string) (device DeviceInfo, err error) { } func (tm *IotMgr) CreateDevice(device DeviceInfo) (deviceResp DeviceInfo, err error) { + if profiling { + profilingTimers["CreateDevice"] = time.Now() + } + log.Info(">>> CreateDevice: ", device) - tm.wg.Wait() - log.Info("GetDevices: After Wait()") + tm.mutex.Lock() + defer tm.mutex.Unlock() - // RequestedMecTrafficRule is not supported yet - if len(device.RequestedMecTrafficRule) != 0 { - err = errors.New("Unsupported traffic rule provided") - log.Error(err.Error()) - return deviceResp, err - } if len(device.RequestedIotPlatformId) != 0 { deviceResp, err = tm.createDeviceWithIotPlatformId(device, device.RequestedIotPlatformId) } else { @@ -414,6 +489,11 @@ func (tm *IotMgr) CreateDevice(device DeviceInfo) (deviceResp DeviceInfo, err er } log.Info("CreateDevice: deviceResp: ", deviceResp) + if profiling { + now := time.Now() + log.Debug("CreateDevice: ", now.Sub(profilingTimers["CreateDevice"])) + } + return deviceResp, nil } @@ -424,8 +504,8 @@ func (tm *IotMgr) DeleteDevice(deviceId string) (err error) { log.Info(">>> DeleteDevice: device: ", deviceId) - tm.wg.Wait() - log.Info("GetDevices: After Wait()") + tm.mutex.Lock() + defer tm.mutex.Unlock() if _, ok := devicesMap[deviceId]; !ok { err = errors.New("Invalid device identifier") @@ -471,36 +551,57 @@ func (tm *IotMgr) createDeviceWithIotPlatformId(device DeviceInfo, requestedIotP err = errors.New("Device already exist") return deviceResp, err } + if _, ok := registeredIotPlatformsAEMap[requestedIotPlatformId]; !ok { + err = errors.New("Devaice cannot be created without oneM2M AE parent") + return deviceResp, err + } - if registeredIotPlatformsMap[requestedIotPlatformId].oneM2M != nil && device.Enabled == true { + // FIXME FSCOM How to manage these fields from DeviceInfo + if registeredIotPlatformsMap[requestedIotPlatformId].oneM2M != nil && device.Enabled { log.Info("createDeviceWithIotPlatformId: Create device on IoT platform", device) var sensor = sssmgr.SensorDiscoveryInfo{ SensorIdentifier: device.DeviceId, - SensorType: "CNT", // FIXME FSCOM How to retrieve this info + SensorType: "FLX", // FIXME FSCOM How to retrieve this info SensorPosition: nil, IotPlatformId: requestedIotPlatformId, } + sensor.Flex = make(map[string]interface{}, 0) + sensor.Flex["type"] = "mec:iotDev" + sensor.Flex["cnd"] = "org.onem2m.common.mec.device.iotDevice" + sensor.Flex["devId"] = device.DeviceId + sensor.Flex["rIPId"] = requestedIotPlatformId + sensor.Flex["enabd"] = device.Enabled + if device.Gpsi != "" { + sensor.Flex["gpsi"] = device.Gpsi + } + if device.Pei != "" { + sensor.Flex["pei"] = device.Pei + } + if device.Supi != "" { + sensor.Flex["supi"] = device.Supi + } + if device.Msisdn != "" { + sensor.Flex["msisdn"] = device.Msisdn + } + if device.Imei != "" { + sensor.Flex["imei"] = device.Imei + } + if device.Imsi != "" { + sensor.Flex["imsi"] = device.Imsi + } + if device.Iccid != "" { + sensor.Flex["iccid"] = device.Iccid + } + if len(device.DeviceMetadata) != 0 { sensor.SensorCharacteristicList = make([]sssmgr.SensorCharacteristic, len(device.DeviceMetadata)) for i, c := range device.DeviceMetadata { sensor.SensorCharacteristicList[i] = sssmgr.SensorCharacteristic{CharacteristicName: c.Key, CharacteristicValue: c.Value} } // End of 'for' statement } - // FIXME FSCOM How to manage these fields from DeviceInfo - // DeviceAuthenticationInfo string - // Gpsi string - // Pei string - // Supi string - // Msisdn string - // Imei string - // Imsi string - // Iccid string - // RequestedMecTrafficRule []TrafficRuleDescriptor - // //DeviceSpecificMessageFormats *DeviceSpecificMessageFormats - // //DownlinkInfo *DownlinkInfo - // ClientCertificate string - // } - sensor, err := registeredIotPlatformsMap[requestedIotPlatformId].oneM2M.OneM2M_create(sensor, "") + log.Info("createDeviceWithIotPlatformId: Call OneM2M_create: ", sensor) + log.Info("createDeviceWithIotPlatformId: registeredIotPlatformsMap: ", registeredIotPlatformsMap) + sensor, err := registeredIotPlatformsMap[requestedIotPlatformId].oneM2M.OneM2M_create(sensor, registeredIotPlatformsAEMap[requestedIotPlatformId].SensorIdentifier) if err != nil { return deviceResp, err } @@ -522,12 +623,26 @@ func (tm *IotMgr) createDeviceWithRequestedUserTransportId(device DeviceInfo, re if val, ok := platformPerUserTransportIdMap[requestedUserTransportId]; ok { deviceResp, err = tm.createDeviceWithIotPlatformId(device, val[0]) } else { - err = errors.New("Invalid UserTransportId") + err = errors.New("Invalid requestedUserTransportId") } if err != nil { log.Error("createDeviceWithIotPlatformId: ", err.Error()) return deviceResp, err } + // FIXME FSCOM How to manage these fields from DeviceInfo + // DeviceAuthenticationInfo string + // Gpsi string + // Pei string + // Supi string + // Msisdn string + // Imei string + // Imsi string + // Iccid string + // RequestedMecTrafficRule []TrafficRuleDescriptor + // //DeviceSpecificMessageFormats *DeviceSpecificMessageFormats + // //DownlinkInfo *DownlinkInfo + // ClientCertificate string + // } log.Info("createDeviceWithIotPlatformId: deviceResp: ", deviceResp) return deviceResp, nil @@ -580,14 +695,24 @@ func (tm *IotMgr) populateDevicesPerIotPlatforms(iotPlatformInfo IotPlatformInfo for _, sensor := range sensors { var deviceInfo = DeviceInfo{ - DeviceId: sensor.SensorIdentifier, - Enabled: true, + DeviceId: sensor.SensorIdentifier, + RequestedIotPlatformId: sensor.IotPlatformId, + Enabled: true, } deviceInfo.DeviceMetadata = make([]KeyValuePair, len(sensor.SensorCharacteristicList)) for i, c := range sensor.SensorCharacteristicList { deviceInfo.DeviceMetadata[i] = KeyValuePair{Key: c.CharacteristicName, Value: c.CharacteristicValue} } // End of 'for' statement + log.Info("populateDevicesPerIotPlatforms: len(sensor.Flex): ", len(sensor.Flex)) + if len(sensor.Flex) != 0 { + for k, v := range sensor.Flex { // Process all flex attributes + log.Info("populateDevicesPerIotPlatforms: key: ", k) + log.Info("populateDevicesPerIotPlatforms: value: ", v) + deviceInfo.DeviceMetadata = append(deviceInfo.DeviceMetadata, KeyValuePair{Key: k, Value: v.(string)}) + } // End of 'for' statement + } + log.Info("populateDevicesPerIotPlatforms: add deviceInfo: ", deviceInfo) devicesMap[deviceInfo.DeviceId] = deviceInfo devicesPerPlatformMap[iotPlatformInfo.IotPlatformId] = append(devicesPerPlatformMap[iotPlatformInfo.IotPlatformId], deviceInfo.DeviceId) } // End of 'for' statement diff --git a/go-packages/meep-iot-mgr/iot-mgr_test.go b/go-packages/meep-iot-mgr/iot-mgr_test.go index 84ca215aa4d2d3c1ae0bff247d0b87452313fda6..b9be619adec006a2b85763026e61112c49e5b4a0 100644 --- a/go-packages/meep-iot-mgr/iot-mgr_test.go +++ b/go-packages/meep-iot-mgr/iot-mgr_test.go @@ -74,10 +74,10 @@ func TestRegisterIotPlatformInfo(t *testing.T) { var userTransportInfo = []MbTransportInfo{} userTransportInfo = append(userTransportInfo, MbTransportInfo{ Id: "d5673793-c55c-4969-b5bc-2121f84b9f8d", - Name: "MQTT", + Name: "laboai-acme-ic-cse", Description: "MQTT", Protocol: "MQTT", - Version: "2", + Version: "3", Endpoint: &endpoint, }) var adresses_1 = []Addresses{} @@ -91,7 +91,7 @@ func TestRegisterIotPlatformInfo(t *testing.T) { } customServicesTransportInfo = append(customServicesTransportInfo, TransportInfo{ Id: "2ddb713c-2b41-4ded-a7ad-a5a047c5df13", - Name: "/laboai-acme-ic-cse", + Name: "laboai-acme-ic-cse", Description: "ACME oneM2M CSE", Protocol: "REST_HTTP", Version: "4", @@ -430,7 +430,7 @@ func registerIotPltf(tm *IotMgr) (iotPlatformInfo IotPlatformInfo, err error) { var userTransportInfo = []MbTransportInfo{} userTransportInfo = append(userTransportInfo, MbTransportInfo{ Id: "d5673793-c55c-4969-b5bc-2121f84b9f8d", - Name: "MQTT", + Name: "laboai-acme-ic-cse", Description: "MQTT", Protocol: "MQTT", Version: "2", @@ -447,7 +447,7 @@ func registerIotPltf(tm *IotMgr) (iotPlatformInfo IotPlatformInfo, err error) { } customServicesTransportInfo = append(customServicesTransportInfo, TransportInfo{ Id: "2ddb713c-2b41-4ded-a7ad-a5a047c5df13", - Name: "/laboai-acme-ic-cse", + Name: "laboai-acme-ic-cse", Description: "ACME oneM2M CSE", Protocol: "REST_HTTP", Version: "4", @@ -481,7 +481,7 @@ func registerIotPltfAndCreateDevice(tm *IotMgr) (iotPlatformInfo IotPlatformInfo var userTransportInfo = []MbTransportInfo{} userTransportInfo = append(userTransportInfo, MbTransportInfo{ Id: "d5673793-c55c-4969-b5bc-2121f84b9f8d", - Name: "MQTT", + Name: "laboai-acme-ic-cse", Description: "MQTT", Protocol: "MQTT", Version: "2", @@ -498,7 +498,7 @@ func registerIotPltfAndCreateDevice(tm *IotMgr) (iotPlatformInfo IotPlatformInfo } customServicesTransportInfo = append(customServicesTransportInfo, TransportInfo{ Id: "2ddb713c-2b41-4ded-a7ad-a5a047c5df13", - Name: "/laboai-acme-ic-cse", + Name: "laboai-acme-ic-cse", Description: "ACME oneM2M CSE", Protocol: "REST_HTTP", Version: "4", diff --git a/go-packages/meep-model/model.go b/go-packages/meep-model/model.go index 97c1030be84c2507fb1e7dd3b0971a8bd86de5be..b22a9cd8a98e66a664c46309973befbbb17435a7 100644 --- a/go-packages/meep-model/model.go +++ b/go-packages/meep-model/model.go @@ -240,6 +240,7 @@ func (m *Model) SetScenario(j []byte) (err error) { m.scenario = scenario err = m.parseNodes() + // No Prob in Parsing Nodes if err != nil { log.Error(err.Error()) return err @@ -368,7 +369,7 @@ func (m *Model) GetServiceMaps() *[]dataModel.NodeServiceMaps { return &m.svcMap } -//UpdateNetChar - Update network characteristics for a node +// UpdateNetChar - Update network characteristics for a node func (m *Model) UpdateNetChar(nc *dataModel.EventNetworkCharacteristicsUpdate, userData interface{}) (err error) { m.lock.Lock() defer m.lock.Unlock() @@ -550,12 +551,6 @@ func (m *Model) addPhyLoc(node *dataModel.ScenarioNode, parentNode *Node) (err e return err } - // Make sure node Name is unique - n := m.nodeMap.FindByName(pl.Name) - if n != nil { - return errors.New("Element " + pl.Name + " already exists in scenario " + m.name) - } - // Ignore any configured processes pl.Processes = make([]dataModel.Process, 0) @@ -581,12 +576,6 @@ func (m *Model) addProcess(node *dataModel.ScenarioNode, parentNode *Node) (err return err } - // Make sure node Name is unique - n := m.nodeMap.FindByName(proc.Name) - if n != nil { - return errors.New("Element " + proc.Name + " already exists in scenario " + m.name) - } - // Add Proc to parent PhyLoc pl.Processes = append(pl.Processes, *proc) @@ -838,7 +827,7 @@ func (m *Model) removeProcess(node *dataModel.ScenarioNode) (err error) { return nil } -//GetScenarioName - Get the scenario name +// GetScenarioName - Get the scenario name func (m *Model) GetScenarioName() string { m.lock.RLock() defer m.lock.RUnlock() @@ -850,7 +839,7 @@ func (m *Model) GetScenarioName() string { return "" } -//GetNodeNames - Get the list of nodes of a certain type; "" or "ANY" returns all +// GetNodeNames - Get the list of nodes of a certain type; "" or "ANY" returns all func (m *Model) GetNodeNames(typ ...string) []string { m.lock.RLock() defer m.lock.RUnlock() @@ -858,11 +847,15 @@ func (m *Model) GetNodeNames(typ ...string) []string { nm := make(map[string]*Node) for _, t := range typ { if t == "" || t == "ANY" { - nm = m.nodeMap.nameMap + for name, nodes := range m.nodeMap.nameMap { + if len(nodes) > 0 { + nm[name] = nodes[0] + } + } break } - for k, v := range m.nodeMap.typeMap[t] { - nm[k] = v + for name, node := range m.nodeMap.FindAllByType(t) { + nm[name] = node } } @@ -873,41 +866,46 @@ func (m *Model) GetNodeNames(typ ...string) []string { return list } -//GetEdges - Get a map of node edges for the current scenario +// GetEdges - Get a map of node edges for the current scenario func (m *Model) GetEdges() (edgeMap map[string]string) { m.lock.RLock() defer m.lock.RUnlock() edgeMap = make(map[string]string) - for k, node := range m.nodeMap.nameMap { - p := reflect.ValueOf(node.parent) - pName := reflect.Indirect(p).FieldByName("Name") - if pName.IsValid() { - edgeMap[k] = pName.String() - // fmt.Printf("%s (%T) \t\t %s(%T)\n", k, node.object, pName, node.parent) + for k, nodes := range m.nodeMap.nameMap { + if len(nodes) > 0 { + node := nodes[0] + p := reflect.ValueOf(node.parent) + pName := reflect.Indirect(p).FieldByName("Name") + if pName.IsValid() { + edgeMap[k] = pName.String() + // fmt.Printf("%s (%T) \t\t %s(%T)\n", k, node.object, pName, node.parent) + } } } return edgeMap } // GetNode - Get a node by its name -// Returned value is of type interface{} -// Good practice: returned node should be type asserted with val,ok := node.(someType) to prevent panic +// +// Returned value is of type interface{} +// Good practice: returned node should be type asserted with val,ok := node.(someType) to prevent panic func (m *Model) GetNode(name string) (node interface{}) { m.lock.RLock() defer m.lock.RUnlock() node = nil n := m.nodeMap.nameMap[name] - if n != nil { - node = n.object + if len(n) > 0 { + node = n[0].object } return node } // GetNodeById - Get a node by its id -// Returned value is of type interface{} -// Returned node may be nil +// +// Returned value is of type interface{} +// Returned node may be nil func (m *Model) GetNodeById(id string) (node interface{}) { m.lock.RLock() defer m.lock.RUnlock() @@ -927,8 +925,8 @@ func (m *Model) GetNodeId(name string) (id string) { id = "" n := m.nodeMap.nameMap[name] - if n != nil { - id = n.id + if len(n) > 0 { + id = n[0].id } return id } @@ -940,8 +938,8 @@ func (m *Model) GetNodeType(name string) (typ string) { typ = "" n := m.nodeMap.nameMap[name] - if n != nil { - typ = n.nodeType + if len(n) > 0 { + typ = n[0].nodeType } return typ } @@ -953,8 +951,8 @@ func (m *Model) GetNodeParent(name string) (parent interface{}) { parent = nil n := m.nodeMap.nameMap[name] - if n != nil { - parent = n.parent + if len(n) > 0 { + parent = n[0].parent } return parent } @@ -966,8 +964,8 @@ func (m *Model) GetNodeChild(name string) (child interface{}) { child = nil n := m.nodeMap.nameMap[name] - if n != nil { - child = n.child + if len(n) > 0 { + child = n[0].child } return child } @@ -979,11 +977,29 @@ func (m *Model) GetNodeContext(name string) (ctx *NodeContext) { ctx = nil n := m.nodeMap.nameMap[name] + if len(n) > 0 { + nodeCtx := n[0].context + var ok bool + if ctx, ok = nodeCtx.(*NodeContext); !ok { + log.Error("Error casting node context for node: " + name) + return nil + } + } + return ctx +} + +// GetNodeContextById - Get a node context by ID +func (m *Model) GetNodeContextById(id string) (ctx *NodeContext) { + m.lock.RLock() + defer m.lock.RUnlock() + + ctx = nil + n := m.nodeMap.idMap[id] if n != nil { nodeCtx := n.context var ok bool if ctx, ok = nodeCtx.(*NodeContext); !ok { - log.Error("Error casting node context for node: " + name) + log.Error("Error casting node context for node ID: " + id) return nil } } @@ -1221,21 +1237,20 @@ func (m *Model) GetProcesses(filter *NodeFilter) dataModel.Processes { m.lock.RLock() defer m.lock.RUnlock() - // Get process nodes - nMap := make(map[string]*Node) - m.mergeNodeMap(nMap, m.nodeMap.FindAllByType(NodeTypeUEApp)) - m.mergeNodeMap(nMap, m.nodeMap.FindAllByType(NodeTypeEdgeApp)) - m.mergeNodeMap(nMap, m.nodeMap.FindAllByType(NodeTypeCloudApp)) - // Find nodes that match filter criteria var processes dataModel.Processes processes.Processes = []dataModel.Process{} - for _, node := range nMap { - if m.filterNode(node, Proc, filter) { - process := *(node.object.(*dataModel.Process)) - - // Append process to list - processes.Processes = append(processes.Processes, process) + types := []string{NodeTypeUEApp, NodeTypeEdgeApp, NodeTypeCloudApp} + for _, typ := range types { + for _, nodes := range m.nodeMap.typeMap[typ] { + for _, node := range nodes { + if m.filterNode(node, Proc, filter) { + process := *(node.object.(*dataModel.Process)) + + // Append process to list + processes.Processes = append(processes.Processes, process) + } + } } } return processes @@ -1316,7 +1331,6 @@ func (m *Model) parseNodes() (err error) { procCtx := NewNodeContext(m.scenario.Name, domain.Name, zone.Name, nl.Name, pl.Name) m.nodeMap.AddNode(NewNode(proc.Id, proc.Name, proc.Type_, proc, nil, pl, procCtx)) m.networkGraph.AddNode(proc.Name, pl.Name, false) - // Update service map for external processes if proc.IsExternal { var nodeServiceMaps dataModel.NodeServiceMaps diff --git a/go-packages/meep-model/nodeMap.go b/go-packages/meep-model/nodeMap.go index 298fbb302cddb2dc12997a84c6f53cd76b98196e..f8c897400a59095e2e35b1a25425d24e24de4497 100644 --- a/go-packages/meep-model/nodeMap.go +++ b/go-packages/meep-model/nodeMap.go @@ -30,16 +30,16 @@ type Node struct { // NodeMap - Model node map type NodeMap struct { idMap map[string]*Node - nameMap map[string]*Node - typeMap map[string]map[string]*Node + nameMap map[string][]*Node + typeMap map[string]map[string][]*Node } // NewNodeMap - allocate a blank NodeMap func NewNodeMap() (nm *NodeMap) { nm = new(NodeMap) nm.idMap = make(map[string]*Node) - nm.nameMap = make(map[string]*Node) - nm.typeMap = make(map[string]map[string]*Node) + nm.nameMap = make(map[string][]*Node) + nm.typeMap = make(map[string]map[string][]*Node) return nm } @@ -59,11 +59,14 @@ func NewNode(id string, name string, nodeType string, object interface{}, child // AddNode - Add a node to the NodeMap func (nm *NodeMap) AddNode(n *Node) { nm.idMap[n.id] = n - nm.nameMap[n.name] = n + nm.nameMap[n.name] = []*Node{n} if nm.typeMap[n.nodeType] == nil { - nm.typeMap[n.nodeType] = make(map[string]*Node) + nm.typeMap[n.nodeType] = make(map[string][]*Node) } - nm.typeMap[n.nodeType][n.name] = n + if nm.typeMap[n.nodeType][n.name] == nil { + nm.typeMap[n.nodeType][n.name] = []*Node{} + } + nm.typeMap[n.nodeType][n.name] = append(nm.typeMap[n.nodeType][n.name], n) } // FindById - find a node using its name @@ -73,15 +76,25 @@ func (nm *NodeMap) FindById(id string) (n *Node) { // FindByName - find a node using its name func (nm *NodeMap) FindByName(name string) (n *Node) { - return nm.nameMap[name] + nodes := nm.nameMap[name] + if len(nodes) > 0 { + return nodes[0] + } + return nil } // FindByType - find a node using its type - NOT SURE WE NEED THIS -func (nm *NodeMap) FindByType(name string, nodeType string) (n *Node) { +func (nm *NodeMap) FindByType(name string, nodeType string) []*Node { return nm.typeMap[nodeType][name] } // FindAllByType - find a list of nodes using a type func (nm *NodeMap) FindAllByType(nodeType string) map[string]*Node { - return nm.typeMap[nodeType] + result := make(map[string]*Node) + for name, nodes := range nm.typeMap[nodeType] { + if len(nodes) > 0 { + result[name] = nodes[0] + } + } + return result } diff --git a/go-packages/meep-model/validator.go b/go-packages/meep-model/validator.go index a102fd46565a6af11d835fc827048101c308378e..67fdfeee47abf97862bc9764bc4097843d076fa0 100644 --- a/go-packages/meep-model/validator.go +++ b/go-packages/meep-model/validator.go @@ -502,6 +502,9 @@ func validateScenario(scenario *dataModel.Scenario) error { return err } + // Name map for elements within this domain + domainNameMap := make(map[string]bool) + // Validate zones for zoneIndex := range domain.Zones { zone := &domain.Zones[zoneIndex] @@ -509,7 +512,7 @@ func validateScenario(scenario *dataModel.Scenario) error { if err := validateUniqueId(zone.Id, idMap); err != nil { return err } - if err := validateUniqueName(zone.Name, nameMap); err != nil { + if err := validateUniqueName(zone.Name, domainNameMap); err != nil { return err } @@ -520,7 +523,7 @@ func validateScenario(scenario *dataModel.Scenario) error { if err := validateUniqueId(nl.Id, idMap); err != nil { return err } - if err := validateUniqueName(nl.Name, nameMap); err != nil { + if err := validateUniqueName(nl.Name, domainNameMap); err != nil { return err } @@ -533,7 +536,7 @@ func validateScenario(scenario *dataModel.Scenario) error { if err := validateUniqueId(pl.Id, idMap); err != nil { return err } - if err := validateUniqueName(pl.Name, nameMap); err != nil { + if err := validateUniqueName(pl.Name, domainNameMap); err != nil { return err } @@ -546,9 +549,6 @@ func validateScenario(scenario *dataModel.Scenario) error { if err := validateUniqueId(proc.Id, idMap); err != nil { return err } - if err := validateUniqueName(proc.Name, nameMap); err != nil { - return err - } } } } diff --git a/go-packages/meep-sss-client/README.md b/go-packages/meep-sss-client/README.md index 6bfa0a4cc2635c4fd08fda90227b5727b6360157..6fe84ed604d91c194d802e39cf24b3952c23bb10 100644 --- a/go-packages/meep-sss-client/README.md +++ b/go-packages/meep-sss-client/README.md @@ -60,7 +60,7 @@ Class | Method | HTTP request | Description - [SensorStatusSubscription](docs/SensorStatusSubscription.md) - [SensorStatusSubscriptionIdBody](docs/SensorStatusSubscriptionIdBody.md) - [ShapeType](docs/ShapeType.md) - - [StatusDataSubscriptionIdBody](docs/StatusDataSubscriptionIdBody.md) + - [SensorDataSubscriptionIdBody](docs/SensorDataSubscriptionIdBody.md) - [SubscriptionLinkList](docs/SubscriptionLinkList.md) - [SubscriptionLinkListLinks](docs/SubscriptionLinkListLinks.md) - [SubscriptionLinkListSubscription](docs/SubscriptionLinkListSubscription.md) diff --git a/go-packages/meep-sss-client/api/swagger.yaml b/go-packages/meep-sss-client/api/swagger.yaml index b77aa69a1d2b495d788250b147f6d20001735258..14057002b7682696f054e6a65197dbbea79a6519 100644 --- a/go-packages/meep-sss-client/api/swagger.yaml +++ b/go-packages/meep-sss-client/api/swagger.yaml @@ -791,7 +791,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/SensorStatusSubscription' + $ref: '#/components/schemas/SensorDataSubscription' x-content-type: application/json "400": description: "Bad Request: used to indicate that incorrect parameters were\ @@ -1577,5 +1577,5 @@ components: status_data_subscriptionId_body: type: object properties: - SensorStatusSubscription: + SensorDataSubscription: $ref: '#/components/schemas/SensorDataSubscription' diff --git a/go-packages/meep-sss-client/api_sensor_data_subscription.go b/go-packages/meep-sss-client/api_sensor_data_subscription.go index c541e06a78dbcf889004fb49099286754d8d13c1..8f9baaa8adc331a191cdaa0221d2392e27ee0c56 100644 --- a/go-packages/meep-sss-client/api_sensor_data_subscription.go +++ b/go-packages/meep-sss-client/api_sensor_data_subscription.go @@ -378,15 +378,15 @@ SensorDataSubscriptionApiService The POST method is used to create a new subscri This method shall support the request and response data structures, and response codes, as specified in Table 7.7.3.4-1 * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @param body The entity body in the request contains data type of the specific sensor status subscription that is to be created -@return []SensorStatusSubscription +@return []SensorDataSubscription */ -func (a *SensorDataSubscriptionApiService) SensorDataSubscriptionPOST(ctx context.Context, body SubscriptionsSensorDataBody) ([]SensorStatusSubscription, *http.Response, error) { +func (a *SensorDataSubscriptionApiService) SensorDataSubscriptionPOST(ctx context.Context, body SubscriptionsSensorDataBody) ([]SensorDataSubscription, *http.Response, error) { var ( localVarHttpMethod = strings.ToUpper("Post") localVarPostBody interface{} localVarFileName string localVarFileBytes []byte - localVarReturnValue []SensorStatusSubscription + localVarReturnValue []SensorDataSubscription ) // create path and map variables @@ -445,7 +445,7 @@ func (a *SensorDataSubscriptionApiService) SensorDataSubscriptionPOST(ctx contex error: localVarHttpResponse.Status, } if localVarHttpResponse.StatusCode == 201 { - var v []SensorStatusSubscription + var v []SensorDataSubscription err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type")) if err != nil { newErr.error = err.Error() @@ -498,7 +498,7 @@ This method shall support the URI query parameters, request and response data st * @param subscriptionId Unique identifiers of a subscription @return []SensorDataSubscription */ -func (a *SensorDataSubscriptionApiService) SensorDataSubscriptionPUT(ctx context.Context, body StatusDataSubscriptionIdBody, subscriptionId string) ([]SensorDataSubscription, *http.Response, error) { +func (a *SensorDataSubscriptionApiService) SensorDataSubscriptionPUT(ctx context.Context, body SensorDataSubscriptionIdBody, subscriptionId string) ([]SensorDataSubscription, *http.Response, error) { var ( localVarHttpMethod = strings.ToUpper("Put") localVarPostBody interface{} diff --git a/go-packages/meep-sss-client/api_sensor_discovery_subscription.go b/go-packages/meep-sss-client/api_sensor_discovery_subscription.go index d7ba1d57ee6e9e9296f23db60472c2da9273bb88..e8a9219b4101c2d840371d6a72d508e727a658db 100644 --- a/go-packages/meep-sss-client/api_sensor_discovery_subscription.go +++ b/go-packages/meep-sss-client/api_sensor_discovery_subscription.go @@ -264,12 +264,12 @@ SensorDiscoverySubscriptionApiService The GET method is used to request informat This method shall support the URI query parameter, request and response data structures, and response codes, as specified in Tables 7.4.3.1-1 and 7.4.3.1-2. * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). * @param optional nil or *SensorDiscoverySubscriptionApiSensorDiscoverySubscriptionGETOpts - Optional Parameters: - * @param "SubscriptionId" (optional.String) - Object containing the characteristics of the sensor(s) to be selected for the query + * @param "sensorIdentifier" (optional.String) - Object containing the characteristics of the sensor(s) to be selected for the query @return []SubscriptionLinkList */ type SensorDiscoverySubscriptionApiSensorDiscoverySubscriptionGETOpts struct { - SubscriptionId optional.String + SensorIdentifierd optional.String } func (a *SensorDiscoverySubscriptionApiService) SensorDiscoverySubscriptionGET(ctx context.Context, localVarOptionals *SensorDiscoverySubscriptionApiSensorDiscoverySubscriptionGETOpts) ([]SubscriptionLinkList, *http.Response, error) { diff --git a/go-packages/meep-sss-client/model_status_data_subscription_id_body.go b/go-packages/meep-sss-client/model_status_data_subscription_id_body.go index 0b09d725c28b1300586e7ff603315696f0f8cfa3..a1af61a4e4988f9b6f333cbbc2d4ad5cd61016fa 100644 --- a/go-packages/meep-sss-client/model_status_data_subscription_id_body.go +++ b/go-packages/meep-sss-client/model_status_data_subscription_id_body.go @@ -9,6 +9,6 @@ */ package client -type StatusDataSubscriptionIdBody struct { - SensorStatusSubscription *SensorDataSubscription `json:"SensorStatusSubscription,omitempty"` +type SensorDataSubscriptionIdBody struct { + SensorDataSubscription *SensorDataSubscription `json:"SensorStatusSubscription,omitempty"` } diff --git a/go-packages/meep-sss-mgr/http.go b/go-packages/meep-sss-mgr/http.go index ce9fcc5f11d5003d99c856b3629a72257b29dd46..ebebda3d1fbbff22ca7a87c70abe1dbf9695c604 100644 --- a/go-packages/meep-sss-mgr/http.go +++ b/go-packages/meep-sss-mgr/http.go @@ -31,14 +31,15 @@ func NewSssMgrHttp() (http_mgr *SssMgrHttp) { } func (http_mgr *SssMgrHttp) init(tm *SssMgr, notify func(map[string]interface{})) (err error) { - log.Info(">>> init") + log.Info(">>> init: cse_name: ", tm.cse_name, " host_id: ", tm.hostId) http_mgr.notify = notify go func() { - log.Info("Init: Starting OneM2M Notification server") - http.HandleFunc("/", http_mgr.handleRoot) - err := http.ListenAndServe(":31122", nil) + log.Info("Init: Starting OneM2M Notification server on /" + tm.cse_name + ", port: " + tm.hostId) + http.HandleFunc("/"+tm.cse_name, http_mgr.handleRoot) + //err := http.ListenAndServe(":31122", nil) + err := http.ListenAndServe(":"+tm.hostId, nil) if err != nil { log.Error(err.Error()) return @@ -71,7 +72,6 @@ func (http_mgr *SssMgrHttp) handleRoot(w http.ResponseWriter, r *http.Request) { return } log.Info("handleRoot: body: ", body) - //map[m2m:sgn:map[nev:map[net:3 rep:map[m2m:cnt:map[cbs:0 cni:0 ct:20250327T111057,824274 et:20300326T111032,133324 lt:20250327T111057,824274 mbs:10000 mni:10 pi:C7feaadbb0400 ri:cnt8465776292050557472 rn:test st:0 ty:3]]] sur:/laboai-acme-ic-cse/sub3916638126520907910]] if _, ok := body["m2m:sgn"]; !ok { err := errors.New("Only m2m:sgn is expected") log.Error(err.Error()) diff --git a/go-packages/meep-sss-mgr/mqtt.go b/go-packages/meep-sss-mgr/mqtt.go index 9eec0dde79d4c506852262f62f4d2adb553f0f3a..e5e1dfb133d57cbbc84df2cb2a5f3a9d7ce55f9f 100644 --- a/go-packages/meep-sss-mgr/mqtt.go +++ b/go-packages/meep-sss-mgr/mqtt.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "sync" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" @@ -30,7 +31,7 @@ func onMessageReceived(client mqtt.Client, msg mqtt.Message) { // if _notify != nil { // ... // } else { - // log.Info("onMessageReceived: null pointer for the callbacl") + // log.Info("onMessageReceived: null pointer for the callback") // } }() } @@ -41,7 +42,7 @@ func onMessageReceivedReq(client mqtt.Client, msg mqtt.Message) { // if _notify != nil { // ... // } else { - // log.Info("onMessageReceivedReq: null pointer for the callbacl") + // log.Info("onMessageReceivedReq: null pointer for the callback") // } }() } @@ -54,12 +55,57 @@ func onMessageReceivedResp(client mqtt.Client, msg mqtt.Message) { }() } +func onMessageReceivedNot(client mqtt.Client, msg mqtt.Message) { + go func() { + log.Info("onMessageReceivedNot: Received message: ", string(msg.Payload()), " on topic ", msg.Topic()) + //{\"fr\": \"/laboai-acme-ic-cse\", \"to\": \"mqtt://172.29.10.56:1883\", \"ot\": \"20250327T134413,103223\", \"op\": 5, \"rqi\": \"6400213617020980613\", \"rvi\": \"4\", \"drt\": 1, \"pc\": {\"m2m:sgn\": {\"nev\": {\"net\": 3, \"rep\": {\"m2m:cin\": {\"cnf\": \"text/plain:0\", \"con\": \"toto\", \"rn\": \"1234\", \"ri\": \"cin3503344383965775257\", \"pi\": \"cnt1992620086833081496\", \"ct\": \"20250327T134413,093784\", \"lt\": \"20250327T134413,093784\", \"ty\": 4, \"cs\": 4, \"st\": 2, \"et\": \"20300326T134347,108295\"}}}, \"sur\": \"/laboai-acme-ic-cse/sub3169944238694067800\"}}} on topic /oneM2M/req/laboai-acme-ic-cse/mqtt:::172.29.10.56:1883/json" + // Prepare response + s := strings.Split(msg.Topic(), "/") // /oneM2M/req/laboai-acme-ic-cse/mqtt:::172.29.10.56:1883/json + resp_topic := "/oneM2M/resp/" + s[4] + "/" + s[3] + "/" + s[5] + log.Debug("onMessageReceivedNot: resp_topic: ", resp_topic) + if _notify != nil { + var d map[string]interface{} + err := json.Unmarshal(msg.Payload(), &d) + if err != nil { + log.Warn("onMessageReceivedNot: ", err.Error()) + return + } + if pc, ok := d["pc"]; ok { + log.Debug("onMessageReceivedNot: pc: ", pc) + log.Debug("onMessageReceivedNot: TypeOf(pc): ", reflect.TypeOf(pc)) + if _, ok := d["pc"].(map[string]interface{}); !ok { + log.Warn("onMessageReceivedNot: pc entry has an unexpected type") + return + } + body := d["pc"].(map[string]interface{}) + if _, ok := body["m2m:sgn"]; !ok { + log.Error("Only m2m:sgn is expected") + return + } + if _, ok := body["m2m:sgn"].(map[string]interface{}); !ok { + log.Warn("onMessageReceivedNot: m2m:sgn entry has an unexpected type") + return + } + m := body["m2m:sgn"].(map[string]interface{}) + log.Info("handleRoot: m: ", m) + _notify(m) + } else { + log.Warn("onMessageReceivedNot: pc entry not found") + return + } + } else { + log.Debug("onMessageReceivedNot: null pointer for the callback") + } + // FIXME FSCOM Send the response + }() +} + var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) { - log.Info("mqtt.OnConnectHandler: Connected") + //log.Info("mqtt.OnConnectHandler: Connected") } var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) { - log.Info("Connect lost:", err) + //log.Info("Connect lost:", err) } func NewSssMgrMqtt() (broker_mqtt *SssMgrMqtt) { @@ -76,10 +122,11 @@ func (broker_mqtt *SssMgrMqtt) init(tm *SssMgr, notify func(map[string]interface broker_mqtt.opts = mqtt.NewClientOptions() broker_mqtt.opts.SetDefaultPublishHandler(onMessageReceived) - broker_mqtt.opts.SetClientID("AdvantEDGE.meep-vis-traffic-mgr") + broker_mqtt.opts.SetClientID("AdvantEDGE.meep-sss-mgr") broker_mqtt.opts.OnConnect = connectHandler broker_mqtt.opts.OnConnectionLost = connectLostHandler broker_mqtt.opts.CleanSession = true + //broker_mqtt.opts.ResumeSubs = true broker_mqtt.opts.SetUsername("") broker_mqtt.opts.SetPassword("") log.Info("init: Add brocker: ", fmt.Sprintf("tcp://%s:%d", tm.host, tm.port)) @@ -95,16 +142,28 @@ func (broker_mqtt *SssMgrMqtt) init(tm *SssMgr, notify func(map[string]interface token.Wait() // Subscribe - log.Info("init: Subscribe to: ", "oneM2M/req/+") - token = broker_mqtt.client.Subscribe("oneM2M/req/+", 0, onMessageReceivedReq) // qos:0 + // tp := "oneM2M/req/+/" + tm.cse_name + "/#" + // log.Info("init: Subscribe to: ", tp) + // token = broker_mqtt.client.Subscribe(tp, 0, onMessageReceivedReq) // qos:0 + // if token.Error() != nil { + // log.Error(token.Error()) + // return token.Error() + // } + // token.Wait() + tp := "/oneM2M/resp/+/" + tm.cse_name + "/#" + log.Info("init: Subscribe to: ", tp) + token = broker_mqtt.client.Subscribe(tp, 0, onMessageReceivedResp) // qos:0 if token.Error() != nil { + broker_mqtt.client.Disconnect(250) log.Error(token.Error()) return token.Error() } - // token.Wait() - log.Info("init: Subscribe to: ", "/oneM2M/resp/#") - token = broker_mqtt.client.Subscribe("/oneM2M/resp/#", 0, onMessageReceivedResp) // qos:0 + token.Wait() + tp = "/oneM2M/req/" + tm.cse_name + "/+/#" + log.Info("init: Subscribe to: ", tp) + token = broker_mqtt.client.Subscribe(tp, 1, onMessageReceivedNot) // qos:1 if token.Error() != nil { + broker_mqtt.client.Disconnect(250) log.Error(token.Error()) return token.Error() } @@ -210,6 +269,7 @@ func (broker_mqtt *SssMgrMqtt) send(p_ctx SssMgrBindingProtocolContext) (err err log.Debug("send: Start waiting for the response") _sync_response.Add(1) _sync_response.Wait() + log.Debug("send: Got the response") if val, ok := _responses[token.(*mqtt.PublishToken).MessageID()]; ok { delete(_responses, token.(*mqtt.PublishToken).MessageID()) log.Debug("send: Get the response: ", string(val.Payload())) @@ -222,19 +282,6 @@ func (broker_mqtt *SssMgrMqtt) send(p_ctx SssMgrBindingProtocolContext) (err err if r, ok := d["pc"]; ok { log.Debug("send: r: ", r) log.Debug("send: TypeOf(r): ", reflect.TypeOf(r)) - // var b []byte - // b, err = json.Marshal(r) - // if err != nil { - // log.Error("send: ", err.Error()) - // return err, nil - // } - // log.Info("send: b: ", b) - // log.Info("send: TypeOf(b): ", reflect.TypeOf(b)) - // err = json.Unmarshal(b, &resp) - // if err != nil { - // log.Error("send: ", err.Error()) - // return err, nil - // } return nil, r } return err, nil diff --git a/go-packages/meep-sss-mgr/onem2m-mgr.go b/go-packages/meep-sss-mgr/onem2m-mgr.go index ceebba77f64920186eb82b0434c1b7e6981f24b5..f5909a1d051585dacdf3c53d4384420e474fb92f 100644 --- a/go-packages/meep-sss-mgr/onem2m-mgr.go +++ b/go-packages/meep-sss-mgr/onem2m-mgr.go @@ -35,12 +35,12 @@ type SssMgr struct { name string namespace string bindingProtocol string + protocol SssMgrBindingProtocol host string port int cse_name string hostId string mutex sync.Mutex - wg sync.WaitGroup refreshTicker *time.Ticker sss_discovery_notify func(map[string]interface{}) sss_status_notify func(map[string]interface{}) @@ -72,23 +72,35 @@ type SensorDiscoveryInfo struct { SensorCharacteristicList []SensorCharacteristic SensorPosition *Point IotPlatformId string + Flex map[string]interface{} +} + +type SensorDiscoveryEventSubscription struct { + SubscriptionType string + SensorInfoList []string + GeographicalArea []AreaInfo +} + +type AreaInfo struct { + Shape string + Points []Point + Radius int32 } var registeredIotPlatformsMap = map[string]IotPlatformInfo{} // List of discovered IOT Plateform var sensorsMap = map[string]SensorDiscoveryInfo{} // Map sensors by sensorIdentifier var sensorsPerPlatformMap = map[string][]string{} // Map dsensorIdentifiers per platform +var subscriptionListPerSubId map[string]SensorDiscoveryInfo // Timer to refresh devices list for all IoT platform -const refreshTickerExpeary = 10 // In seconds +const refreshTickerExpeary = 20 // In seconds // Enable profiling const profiling = false var profilingTimers map[string]time.Time -var protocol SssMgrBindingProtocol - -var subscriptionListPerSubId map[string]SensorDiscoveryInfo +//var protocol SssMgrBindingProtocol // NewSssMgr - Creates and initializes a new SSS Traffic Manager func NewSssMgr(name string, namespace string, bindingProtocol string, host string, port int, hostId string, cse_name string, sss_discovery_notify func(map[string]interface{}), sss_status_notify func(map[string]interface{}), sss_data_notify func(map[string]interface{})) (tm *SssMgr, err error) { @@ -111,6 +123,7 @@ func NewSssMgr(name string, namespace string, bindingProtocol string, host strin tm.port = port tm.cse_name = cse_name tm.hostId = hostId + tm.protocol = nil if tm.bindingProtocol == "MQTT" { if tm.host == "" { err := errors.New("Host not set for MQTTP protocol") @@ -120,12 +133,12 @@ func NewSssMgr(name string, namespace string, bindingProtocol string, host strin if tm.port == 0 { tm.port = 1883 } - protocol = NewSssMgrMqtt() + tm.protocol = NewSssMgrMqtt() } else if tm.bindingProtocol == "REST_HTTP" { if tm.port == 0 { tm.port = 80 } - protocol = NewSssMgrHttp() + tm.protocol = NewSssMgrHttp() } else { err := errors.New("Binding protocol not set") log.Error(err.Error()) @@ -142,7 +155,7 @@ func NewSssMgr(name string, namespace string, bindingProtocol string, host strin return nil, err } - err = protocol.init(tm, tm.notify) + err = tm.protocol.init(tm, tm.notify) if err != nil { log.Error(err.Error()) return nil, err @@ -174,15 +187,17 @@ func (tm *SssMgr) init() { sensorsPerPlatformMap = make(map[string][]string, 0) tm.refreshTicker = nil subscriptionListPerSubId = make(map[string]SensorDiscoveryInfo, 0) + + tm.startRefreshTicker() } // DeleteSssMgr - func (tm *SssMgr) DeleteSssMgr() (err error) { tm.stopRefreshTicker() - if protocol != nil { - protocol.uninit() - protocol = nil + if tm.protocol != nil { + tm.protocol.uninit() + tm.protocol = nil } sensorsMap = nil @@ -193,33 +208,32 @@ func (tm *SssMgr) DeleteSssMgr() (err error) { } func (tm *SssMgr) startRefreshTicker() { - log.Debug("Starting refresh loop") - tm.refreshTicker = time.NewTicker(refreshTickerExpeary * time.Second) + log.Debug(">>> sssmgr.startRefreshTicker: ", time.Duration(refreshTickerExpeary)*time.Second) + tm.refreshTicker = time.NewTicker(time.Duration(refreshTickerExpeary) * time.Second) go func() { - if tm.refreshTicker != nil { - for range tm.refreshTicker.C { - // Refresh the list of devices - tm.wg.Add(1) - err := tm.populateDevicesPerIotPlatforms() - if err != nil { - log.Error(err) - } - tm.wg.Done() + log.Debug("sssmgr.startRefreshTicker: Fired") + for range tm.refreshTicker.C { + // Refresh the list of devices + tm.mutex.Lock() + err := tm.populateDevicesPerIotPlatforms() + tm.mutex.Unlock() + if err != nil { + log.Error(err) } - } + + continue // Infinite loop till stopRefreshTicker is call + } // End of 'for' statement + log.Debug("sssmgr.startRefreshTicker: Leaving time loop") }() } func (tm *SssMgr) stopRefreshTicker() { if tm.refreshTicker != nil { - // Refresh the list of devices - tm.wg.Add(1) tm.refreshTicker.Stop() tm.refreshTicker = nil registeredIotPlatformsMap = nil sensorsMap = nil sensorsPerPlatformMap = nil - tm.wg.Done() log.Debug("Refresh loop stopped") } } @@ -229,15 +243,10 @@ func (tm *SssMgr) SensorDiscoveryInfoAll() (sensors []SensorDiscoveryInfo, err e profilingTimers["SensorDiscoveryInfoAll"] = time.Now() } - log.Info(">>> SensorDiscoveryInfoAll") + log.Info(">>> sssmgr.SensorDiscoveryInfoAll") - err = tm.populateDevicesPerIotPlatforms() // FIXME FSCOM User timer. See startRefreshTicker - if err != nil { - return sensors, err - } - - tm.wg.Wait() - log.Info("SensorDiscoveryInfoAll: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() sensors = make([]SensorDiscoveryInfo, 0) if len(registeredIotPlatformsMap) == 0 { @@ -245,14 +254,14 @@ func (tm *SssMgr) SensorDiscoveryInfoAll() (sensors []SensorDiscoveryInfo, err e } for _, v := range sensorsMap { - log.Info("SensorDiscoveryInfoAll: adding sensor: ", v) + log.Info("sssmgr.SensorDiscoveryInfoAll: adding sensor: ", v) sensors = append(sensors, v) } // End of 'for' statement - log.Info("SensorDiscoveryInfoAll: sensors: ", sensors) + log.Info("sssmgr.SensorDiscoveryInfoAll: sensors: ", sensors) if profiling { now := time.Now() - log.Debug("SensorDiscoveryInfoAll: ", now.Sub(profilingTimers["SensorDiscoveryInfoAll"])) + log.Debug("sssmgr.SensorDiscoveryInfoAll: ", now.Sub(profilingTimers["SensorDiscoveryInfoAll"])) } return sensors, nil @@ -265,8 +274,8 @@ func (tm *SssMgr) GetSensor(sensorIdentifier string) (sensor SensorDiscoveryInfo log.Info(">>> GetSensor: sensorIdentifier: ", sensorIdentifier) - tm.wg.Wait() - log.Info("GetSensor: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() if val, ok := sensorsMap[sensorIdentifier]; !ok { err = errors.New("Wrong Device identifier") @@ -290,9 +299,6 @@ func (tm *SssMgr) GetSensor(sensorIdentifier string) (sensor SensorDiscoveryInfo */ func (tm *SssMgr) populateDevicesPerIotPlatforms() error { - tm.mutex.Lock() - defer tm.mutex.Unlock() - if profiling { profilingTimers["populateDevicesPerIotPlatforms"] = time.Now() } @@ -304,12 +310,18 @@ func (tm *SssMgr) populateDevicesPerIotPlatforms() error { // Refresh the list of devices for all registered Iot platform for _, iotPlatform := range registeredIotPlatformsMap { log.Debug("populateDevicesPerIotPlatforms: processing: ", iotPlatform.Address) - err := tm.populateSensors(iotPlatform) + err := tm.populateSensors(iotPlatform, "3") + if err != nil { + log.Error("populateDevicesPerIotPlatforms: ", err) + continue + } + err = tm.populateSensors(iotPlatform, "28") if err != nil { log.Error("populateDevicesPerIotPlatforms: ", err) continue } } // End of 'for' statement + log.Debug("populateDevicesPerIotPlatforms: After loop") if profiling { now := time.Now() @@ -324,7 +336,7 @@ func (tm *SssMgr) populateDevicesPerIotPlatforms() error { * @param {string} iotPlatformId contains the IoT platform identifier * @return {struct} nil on success, error otherwise */ -func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo) error { +func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo, type_ string) error { if profiling { profilingTimers["populateSensors"] = time.Now() } @@ -348,12 +360,12 @@ func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo) error { // Build the queries queries := map[string]string{} queries["fu"] = "1" // Filter usage - queries["ty"] = "3" // FIXME FSCOM Filter on oneM2M CNT for sensors or on AE because AE if the platform and CNT is a sensor and CIN the values + queries["ty"] = type_ ctx.queries = queries - err, resp := protocol.send(ctx) + err, resp := tm.protocol.send(ctx) if err != nil { - log.Error("OneM2M_create: ", err.Error()) + log.Error("populateSensors: ", err.Error()) return err } log.Debug("populateSensors: resp: ", resp) @@ -362,7 +374,7 @@ func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo) error { log.Debug("populateSensors: oneM2M_uril: ", oneM2M_uril) log.Debug("populateSensors: TypeOf(oneM2M_uril): ", reflect.TypeOf(oneM2M_uril)) log.Debug("populateSensors: len(oneM2M_uril): ", len(oneM2M_uril)) - // Loop for each CIN and build the sensor list + // Loop for each element and build the sensor list for _, v := range oneM2M_uril["m2m:uril"].([]interface{}) { log.Debug("populateSensors: Processing key: v: ", v) log.Debug("populateSensors: Processing key: TypeOf(v): ", reflect.TypeOf(v)) @@ -373,14 +385,14 @@ func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo) error { } ctx.to = s ctx.queries["fu"] = "2" - err, resp := protocol.send(ctx) + err, resp := tm.protocol.send(ctx) if err != nil { - log.Error("OneM2M_create: ", err.Error()) + log.Error("populateSensors: ", err.Error()) continue } log.Debug("populateSensors: resp: ", resp) log.Debug("populateSensors: type(resp): ", reflect.TypeOf(resp)) - if resp.(map[string]interface{}) == nil || resp.(map[string]interface{})["m2m:cnt"] == nil { + if resp.(map[string]interface{}) == nil { continue } // oneM2M_cin := resp.(map[string]interface{})["m2m:cnt"].(map[string]interface{}) @@ -390,15 +402,42 @@ func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo) error { var sensor = SensorDiscoveryInfo{ IotPlatformId: iotPlatformInfo.IotPlatformId, } + if type_ == "28" { + sensor.SensorType = "FLX" + // TODO To be removed sensor.Flex = map[string]interface{}{} // FIXME FSCOM How to create flex container map from list of attributes recieved? Q&D: extract all attributes != attributes[AE/CNT/CIN]??? + // ==> Use cnd which contains the link to download the onthology or the reference to the oneM2M standard + } sensor, err = tm.oneM2M_deserialize(sensor, resp.(map[string]interface{})) if err != nil { log.Warn("populateSensors: ", err.Error()) continue } - log.Info("populateSensors: sensor: ", sensor) + //log.Info("populateSensors: Before sensorsMap: ", sensorsMap) + if _, ok := sensorsMap[sensor.SensorIdentifier]; ok { + delete(sensorsMap, sensor.SensorIdentifier) + } sensorsMap[sensor.SensorIdentifier] = sensor - sensorsPerPlatformMap[sensor.IotPlatformId] = append(sensorsPerPlatformMap[sensor.IotPlatformId], sensor.SensorIdentifier) + //log.Info("populateSensors: After sensorsMap: ", sensorsMap) + log.Info("populateSensors: Before sensorsPerPlatformMap: ", sensorsPerPlatformMap) + if len(sensorsPerPlatformMap[sensor.IotPlatformId]) == 0 { + sensorsPerPlatformMap[sensor.IotPlatformId] = append(sensorsPerPlatformMap[sensor.IotPlatformId], sensor.SensorIdentifier) + } else { // Replace or add a new element + found := -1 + for i, v := range sensorsPerPlatformMap[sensor.IotPlatformId] { + if v == sensor.SensorIdentifier { + found = i + break + } + } // End of 'for' statement + log.Info("populateSensors: found idx: ", found) + if found != -1 { // Replace + sensorsPerPlatformMap[sensor.IotPlatformId] = append(sensorsPerPlatformMap[sensor.IotPlatformId][:found], sensorsPerPlatformMap[sensor.IotPlatformId][found+1:]...) + } + // Add + sensorsPerPlatformMap[sensor.IotPlatformId] = append(sensorsPerPlatformMap[sensor.IotPlatformId], sensor.SensorIdentifier) + } + log.Info("populateSensors: After sensorsPerPlatformMap: ", sensorsPerPlatformMap) } // End of 'for' statement log.Info("populateSensors: sensorsMap: ", sensorsMap) @@ -412,6 +451,17 @@ func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo) error { return nil } +func (tm *SssMgr) ProcessSensorDiscoveryEventSubscription(sensorDiscoveryEventSubscription SensorDiscoveryEventSubscription) (err error) { + log.Info("sbi.processSensorDiscoveryEventSubscription: ", sensorDiscoveryEventSubscription) + + return nil +} + +/**** + * OneM2M_create - MEC IPE implemntation + * FIXME To be reworked + ****/ + func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensorResp SensorDiscoveryInfo, err error) { if profiling { @@ -426,8 +476,8 @@ func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensor return sensorResp, err } - tm.wg.Wait() - log.Info("OneM2M_create: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() // Create the initial payload dictionary var bodyMap = map[string]map[string]interface{}{} @@ -454,7 +504,11 @@ func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensor if len(sensor.SensorCharacteristicList) != 0 { for _, val := range sensor.SensorCharacteristicList { log.Debug("OneM2M_create: Adding CNT metadata: ", val) - bodyMap["m2m:cnt"][val.CharacteristicName] = val.CharacteristicValue + if val.CharacteristicName == "lbl" || val.CharacteristicName == "label" { + bodyMap["m2m:cnt"][val.CharacteristicName] = []string{val.CharacteristicValue} + } else { + bodyMap["m2m:cnt"][val.CharacteristicName] = val.CharacteristicValue + } } // End of 'for' statement } } else if sensor.SensorType == "CIN" { @@ -468,6 +522,38 @@ func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensor bodyMap["m2m:cin"][val.CharacteristicName] = val.CharacteristicValue } // End of 'for' statement } + } else if sensor.SensorType == "FLX" { + // Sanity checks + if sensor.Flex == nil { + err = errors.New("flex parameter shall be present") + log.Error("OneM2M_create: ", err.Error()) + return sensorResp, err + } else if _, ok := sensor.Flex["type"]; !ok { + err = errors.New("'type' entry is required") + log.Error("OneM2M_create: ", err.Error()) + return sensorResp, err + } else if _, ok := sensor.Flex["type"].(string); !ok { + err = errors.New("'type' entry is required") + log.Error("OneM2M_create: ", err.Error()) + return sensorResp, err + } else if _, ok := sensor.Flex["cnd"]; !ok { + err = errors.New("'cnd' entry is required") + log.Error("OneM2M_create: ", err.Error()) + return sensorResp, err + } else if _, ok := sensor.Flex["cnd"].(string); !ok { + err = errors.New("'cnd' entry is required") + log.Error("OneM2M_create: ", err.Error()) + return sensorResp, err + } + k := sensor.Flex["type"].(string) + bodyMap[k] = make(map[string]interface{}, 0) + bodyMap[k]["rn"] = sensor.SensorIdentifier + for i, v := range sensor.Flex { + if i == "type" { + continue // skip it + } + bodyMap[k][i] = v + } // End of 'for' statement } else { err = errors.New("OneM2M_create: Invalid type") log.Error("OneM2M_create: ", err.Error()) @@ -496,13 +582,15 @@ func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensor ctx.ty = 3 } else if sensor.SensorType == "CIN" { ctx.ty = 4 + } else if sensor.SensorType == "FLX" { + ctx.ty = 28 } else { err = errors.New("OneM2M_create: Invalid type") log.Error("send: ", err.Error()) return sensorResp, err } - err, resp := protocol.send(ctx) + err, resp := tm.protocol.send(ctx) if err != nil { log.Error("OneM2M_create: ", err.Error()) return sensorResp, err @@ -510,7 +598,13 @@ func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensor log.Debug("OneM2M_create: resp: ", resp) log.Debug("OneM2M_create: TypeOf(resp): ", reflect.TypeOf(resp)) if _, ok := resp.(map[string]interface{}); !ok { - log.Error("OneM2M_create: Interface not available") + err = errors.New("Interface not available") + log.Error("OneM2M_create: ", err.Error()) + return sensorResp, err + } + if val, ok := resp.(map[string]interface{})["m2m:dbg"]; ok { + err = errors.New(val.(string)) + log.Error("OneM2M_create: ", err.Error()) return sensorResp, err } @@ -519,6 +613,7 @@ func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensor sensorResp.SensorType = sensor.SensorType sensorResp.IotPlatformId = sensor.IotPlatformId sensorResp.SensorPosition = sensor.SensorPosition + sensorResp.Flex = sensor.Flex sensorResp, err = tm.oneM2M_deserialize(sensorResp, resp.(map[string]interface{})) if err != nil { log.Error("OneM2M_create: ", err.Error()) @@ -549,8 +644,8 @@ func (tm *SssMgr) OneM2M_discovery(type_ string, iotPlatformId string) (sensorRe return nil, err } - tm.wg.Wait() - log.Info("OneM2M_discovery: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() // 1. Get the list of the AE // Build the context @@ -585,9 +680,9 @@ func (tm *SssMgr) OneM2M_discovery(type_ string, iotPlatformId string) (sensorRe } ctx.queries = queries - err, resp := protocol.send(ctx) + err, resp := tm.protocol.send(ctx) if err != nil { - log.Error("OneM2M_create: ", err.Error()) + log.Error("OneM2M_discovery: ", err.Error()) return nil, err } log.Debug("OneM2M_discovery: resp: ", resp) @@ -640,8 +735,8 @@ func (tm *SssMgr) OneM2M_get(path string, iotPlatformId string) (sensorResp Sens return sensorResp, err } - tm.wg.Wait() - log.Info("OneM2M_get: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() // 1. Get the list of the AE // Build the context @@ -658,7 +753,7 @@ func (tm *SssMgr) OneM2M_get(path string, iotPlatformId string) (sensorResp Sens code: 200, } - err, resp := protocol.send(ctx) + err, resp := tm.protocol.send(ctx) if err != nil { log.Error("OneM2M_get: ", err.Error()) return sensorResp, err @@ -702,8 +797,8 @@ func (tm *SssMgr) OneM2M_subscribe(iotPlatformId string, path string) (subscript return "", err } - tm.wg.Wait() - log.Info("OneM2M_subscribe: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() // 1. Get the list of the AE // Build the context @@ -722,13 +817,17 @@ func (tm *SssMgr) OneM2M_subscribe(iotPlatformId string, path string) (subscript var bodyMap = map[string]map[string]interface{}{} bodyMap["m2m:sub"] = make(map[string]interface{}, 0) net := make(map[string][]int) - net["net"] = []int{2, 3, 4} + net["net"] = []int{1, 2, 3, 4} bodyMap["m2m:sub"]["enc"] = net - bodyMap["m2m:sub"]["nu"] = []string{"http://172.29.10.52:31122/"} // FIXME FSCOM The URI of the listener + if tm.bindingProtocol == "MQTT" { + bodyMap["m2m:sub"]["nu"] = []string{"mqtt://172.29.10.56:1883"} // FIXME FSCOM The URI of the listener + } else { + bodyMap["m2m:sub"]["nu"] = []string{"http://172.29.10.52:31122/"} // FIXME FSCOM The URI of the listener + } bodyMap["m2m:sub"]["rn"] = uuid.New().String() ctx.body = bodyMap - err, resp := protocol.send(ctx) + err, resp := tm.protocol.send(ctx) if err != nil { log.Error("OneM2M_subscribe: ", err.Error()) return "", err @@ -736,7 +835,7 @@ func (tm *SssMgr) OneM2M_subscribe(iotPlatformId string, path string) (subscript log.Debug("OneM2M_subscribe: resp: ", resp) log.Debug("OneM2M_subscribe: TypeOf(resp): ", reflect.TypeOf(resp)) if _, ok := resp.(map[string]interface{}); !ok { - log.Error("OneM2M_create: Interface not available") + log.Error("OneM2M_subscribe: Interface not available") return "", err } @@ -759,7 +858,7 @@ func (tm *SssMgr) OneM2M_subscribe(iotPlatformId string, path string) (subscript log.Error("OneM2M_subscribe: ", err.Error()) return "", err } - log.Debug("OneM2M_cOneM2M_subscribereate: sensvorResp: ", v) + log.Debug("OneM2M_subscribe: v: ", v) subscriptionListPerSubId[subId] = v @@ -773,28 +872,28 @@ func (tm *SssMgr) OneM2M_subscribe(iotPlatformId string, path string) (subscript return subId, nil } -func (tm *SssMgr) OneM2M_Delete(sensor SensorDiscoveryInfo) (err error) { +func (tm *SssMgr) OneM2M_delete(sensor SensorDiscoveryInfo) (err error) { if profiling { - profilingTimers["OneM2M_Delete"] = time.Now() + profilingTimers["OneM2M_delete"] = time.Now() } - log.Info(">>> OneM2M_Delete: sensor=", sensor) + log.Info(">>> OneM2M_delete: sensor=", sensor) if sensor.SensorIdentifier == "" { - err = errors.New("OneM2M_Delete: Cannot find \"ri\" value") - log.Error("OneM2M_Delete: ", err.Error()) + err = errors.New("OneM2M_delete: Cannot find \"ri\" value") + log.Error("OneM2M_delete: ", err.Error()) return err } if sensor.IotPlatformId == "" { err = errors.New("IotPlatformId fiels shall be set") - log.Error("OneM2M_Delete: ", err.Error()) + log.Error("OneM2M_delete: ", err.Error()) return err } - tm.wg.Wait() - log.Info("OneM2M_Delete: After Synchro") + tm.mutex.Lock() + defer tm.mutex.Unlock() // Send it and get the result var ctx = SssMgrBindingProtocolContext{ @@ -809,43 +908,43 @@ func (tm *SssMgr) OneM2M_Delete(sensor SensorDiscoveryInfo) (err error) { rvi: []string{"4"}, // FIXME FSCOM How to get it code: 200, } - err, _ = protocol.send(ctx) + err, _ = tm.protocol.send(ctx) if err != nil { - log.Error("OneM2M_Delete: ", err.Error()) + log.Error("OneM2M_delete: ", err.Error()) return err } if profiling { now := time.Now() - log.Debug("OneM2M_Delete: ", now.Sub(profilingTimers["OneM2M_Delete"])) + log.Debug("OneM2M_delete: ", now.Sub(profilingTimers["OneM2M_delete"])) } return nil } -func (tm *SssMgr) OneM2M_DeleteSub(subId string) (err error) { +func (tm *SssMgr) OneM2M_delete_subscription(subId string) (err error) { if profiling { - profilingTimers["OneM2M_DeleteSub"] = time.Now() + profilingTimers["OneM2M_delete_subscription"] = time.Now() } - log.Info(">>> OneM2M_DeleteSub: sensor=", subId) + log.Info(">>> OneM2M_delete_subscription: sensor=", subId) if subId == "" { err = errors.New("subId fiels shall be set") - log.Error("OneM2M_DeleteSub: ", err.Error()) + log.Error("OneM2M_delete_subscription: ", err.Error()) return err } + tm.mutex.Lock() + defer tm.mutex.Unlock() + if _, ok := subscriptionListPerSubId[subId]; !ok { err = errors.New("Unkmown subscription identifier") - log.Error("OneM2M_DeleteSub: ", err.Error()) + log.Error("OneM2M_delete_subscription: ", err.Error()) return err } - tm.wg.Wait() - log.Info("OneM2M_DeleteSub: After Synchro") - // Send it and get the result var ctx = SssMgrBindingProtocolContext{ host: registeredIotPlatformsMap[subscriptionListPerSubId[subId].IotPlatformId].Address, @@ -859,18 +958,18 @@ func (tm *SssMgr) OneM2M_DeleteSub(subId string) (err error) { rvi: []string{"4"}, // FIXME FSCOM How to get it code: 200, } - err, _ = protocol.send(ctx) + err, _ = tm.protocol.send(ctx) if err != nil { - log.Error("OneM2M_DeleteSub: ", err.Error()) + log.Error("OneM2M_delete_subscription: ", err.Error()) return err } delete(subscriptionListPerSubId, subId) - log.Info("OneM2M_DeleteSub: New subscriptionListPerSubId: ", subscriptionListPerSubId) + log.Info("OneM2M_delete_subscription: New subscriptionListPerSubId: ", subscriptionListPerSubId) if profiling { now := time.Now() - log.Debug("OneM2M_DeleteSub: ", now.Sub(profilingTimers["OneM2M_DeleteSub"])) + log.Debug("OneM2M_delete_subscription: ", now.Sub(profilingTimers["OneM2M_delete_subscription"])) } return nil @@ -889,7 +988,7 @@ func (tm *SssMgr) notify(sub map[string]interface{}) { return } if _, ok := sub["nev"].(map[string]interface{}); !ok { - log.Warn("notify: nev entry has unexpected type") + log.Warn("notify: nev entry has an unexpected type") return } if _, ok := sub["sur"]; !ok { @@ -947,116 +1046,199 @@ func (tm *SssMgr) notify(sub map[string]interface{}) { } func (tm *SssMgr) oneM2M_deserialize(sensor SensorDiscoveryInfo, response map[string]interface{}) (sensorResp SensorDiscoveryInfo, err error) { + log.Debug(">>> oneM2M_deserialize: sensor: ", sensor) log.Debug(">>> oneM2M_deserialize: response: ", response) sensorResp = sensor // Same data structure - for i, m := range response { - log.Debug("==> ", i, " value is ", m) - if _, ok := m.(map[string]interface{}); !ok { - // Skip it - log.Warn("oneM2M_deserialize: m is not map[string]interface{}") - continue + if sensor.SensorType == "FLX" { // Extract flex specific attributes first + // // Sanity checks + if len(response) != 1 { + err = errors.New("Wrong 'flex' parameter value") + log.Error("oneM2M_deserialize: ", err.Error()) + return sensorResp, err } - // m is a map[string]interface. - // loop over keys and values in the map. - for k, v := range m.(map[string]interface{}) { - log.Debug(k, " value is ", v) - log.Debug("oneM2M_deserialize: type(v): ", reflect.TypeOf(v)) - - if k == "ri" { - if item, ok := v.(string); ok { - sensorResp.SensorIdentifier = item - } else { - log.Error("oneM2M_deserialize: Failed to process ", k) - } - } else if k == "ty" { - if item, ok := v.(float64); ok { - switch item { - case 2: - sensorResp.SensorType = "AE" - case 3: - sensorResp.SensorType = "CNT" - case 4: - sensorResp.SensorType = "CIN" - default: - sensorResp.SensorType = strconv.FormatFloat(item, 'f', -1, 64) + keys := make([]string, 0, len(response)) + for k := range response { + keys = append(keys, k) + } + s := keys[0] // Resource type. E.g. cod:color + log.Debug("oneM2M_deserialize: Extract flex specific attributes first") + sensorResp.Flex = make(map[string]interface{}) + log.Debug("oneM2M_deserialize: s=", s) + log.Debug("oneM2M_deserialize: response[s]: ", response[s]) + for k, v := range response[s].(map[string]interface{}) { + log.Debug("oneM2M_deserialize: Processing k= ", k, ", v= ", v) + if e, ok := response[s].(map[string]interface{})[k]; ok { + log.Debug("oneM2M_deserialize: ", k, " is a flex attribute: ", e) + sensorResp.Flex[k] = interface_to_string(e) + } + } // End of 'for' statement + log.Debug("oneM2M_deserialize: After s=", s) + sensorResp.Flex["type"] = s + sensorResp.SensorIdentifier = sensorResp.Flex["ri"].(string) + log.Debug("oneM2M_deserialize: response: ", response) + log.Debug("oneM2M_deserialize: sensorResp.Flex: ", sensorResp.Flex) + } else { + for i, m := range response { + log.Debug("==> ", i, " value is ", m) + if _, ok := m.(map[string]interface{}); !ok { + // Skip it + log.Warn("oneM2M_deserialize: m is not map[string]interface{}") + continue + } + // m is a map[string]interface. + // loop over keys and values in the map. + for k, v := range m.(map[string]interface{}) { + log.Debug(k, " value is ", v) + log.Debug("oneM2M_deserialize: type(v): ", reflect.TypeOf(v)) + + if k == "ri" { + if item, ok := v.(string); ok { + sensorResp.SensorIdentifier = item + } else { + log.Error("oneM2M_deserialize: Failed to process ", k) + } + } else if k == "ty" { + if item, ok := v.(float64); ok { + switch item { + case 2: + sensorResp.SensorType = "AE" + case 3: + sensorResp.SensorType = "CNT" + case 4: + sensorResp.SensorType = "CIN" + case 28: + sensorResp.SensorType = "FLX" + default: + sensorResp.SensorType = strconv.FormatFloat(item, 'f', -1, 64) + } + } else { + log.Error("oneM2M_deserialize: Failed to process ", k) } } else { - log.Error("oneM2M_deserialize: Failed to process ", k) - } - } else { - if item, ok := v.(string); ok { - sensorResp.SensorCharacteristicList = append( - sensorResp.SensorCharacteristicList, - SensorCharacteristic{ - CharacteristicName: k, - CharacteristicValue: string(item), - }) - } else if item, ok := v.(float64); ok { - sensorResp.SensorCharacteristicList = append( - sensorResp.SensorCharacteristicList, - SensorCharacteristic{ - CharacteristicName: k, - CharacteristicValue: strconv.FormatFloat(item, 'f', -1, 64), - }) - } else if item, ok := v.(int64); ok { - sensorResp.SensorCharacteristicList = append( - sensorResp.SensorCharacteristicList, - SensorCharacteristic{ - CharacteristicName: k, - CharacteristicValue: strconv.FormatInt(item, 10), - }) - } else if item, ok := v.(bool); ok { sensorResp.SensorCharacteristicList = append( sensorResp.SensorCharacteristicList, SensorCharacteristic{ CharacteristicName: k, - CharacteristicValue: strconv.FormatBool(item), + CharacteristicValue: interface_to_string(v), }) - } else if item, ok := v.([]string); ok { - sensorResp.SensorCharacteristicList = append( - sensorResp.SensorCharacteristicList, - SensorCharacteristic{ - CharacteristicName: k, - CharacteristicValue: strings.Join(item, ","), - }) - } else if item, ok := v.([]int64); ok { - log.Warn("oneM2M_deserialize: Failed to convert list of int64 into string: ", item) - } else if item, ok := v.(map[string]interface{}); ok { - v, err := json.Marshal(item) - if err == nil { - sensorResp.SensorCharacteristicList = append( - sensorResp.SensorCharacteristicList, - SensorCharacteristic{ - CharacteristicName: k, - CharacteristicValue: string(v), - }) - } else { - log.Warn("oneM2M_deserialize: ", err.Error()) - } - } else if item, ok := v.([]interface{}); ok { - log.Debug("oneM2M_deserialize: Got []interface {} for ", k) - log.Debug("oneM2M_deserialize: ValueOf ", reflect.ValueOf(item)) - v, err := json.Marshal(item) - if err == nil { - sensorResp.SensorCharacteristicList = append( - sensorResp.SensorCharacteristicList, - SensorCharacteristic{ - CharacteristicName: k, - CharacteristicValue: string(v), - }) - } else { - log.Warn("oneM2M_deserialize: ", err.Error()) - } - } else { - log.Warn("oneM2M_deserialize: Failed to process: ", k) + // FIXME FSCOM To be removed + // if item, ok := v.(string); ok { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: string(item), + // }) + // } else if item, ok := v.(float64); ok { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: strconv.FormatFloat(item, 'f', -1, 64), + // }) + // } else if item, ok := v.(int64); ok { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: strconv.FormatInt(item, 10), + // }) + // } else if item, ok := v.(bool); ok { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: strconv.FormatBool(item), + // }) + // } else if item, ok := v.([]string); ok { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: strings.Join(item, ","), + // }) + // } else if item, ok := v.([]int64); ok { + // log.Warn("oneM2M_deserialize: Failed to convert list of int64 into string: ", item) + // } else if item, ok := v.(map[string]interface{}); ok { + // v, err := json.Marshal(item) + // if err == nil { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: string(v), + // }) + // } else { + // log.Warn("oneM2M_deserialize: ", err.Error()) + // } + // } else if item, ok := v.([]interface{}); ok { + // log.Debug("oneM2M_deserialize: Got []interface {} for ", k) + // log.Debug("oneM2M_deserialize: ValueOf ", reflect.ValueOf(item)) + // v, err := json.Marshal(item) + // if err == nil { + // sensorResp.SensorCharacteristicList = append( + // sensorResp.SensorCharacteristicList, + // SensorCharacteristic{ + // CharacteristicName: k, + // CharacteristicValue: string(v), + // }) + // } else { + // log.Warn("oneM2M_deserialize: ", err.Error()) + // } + // } else { + // log.Warn("oneM2M_deserialize: Failed to process: ", k) + // } } - } - } // End of 'for' loop + } // End of 'for' loop - } // End of 'for' loop + } // End of 'for' loop + } log.Debug("oneM2M_deserialize: sensorResp: ", sensorResp) return sensorResp, nil } + +func interface_to_string(v interface{}) (s string) { + if item, ok := v.(string); ok { + return string(item) + } else if item, ok := v.(float64); ok { + return strconv.FormatFloat(item, 'f', -1, 64) + } else if item, ok := v.(int64); ok { + return strconv.FormatInt(item, 10) + } else if item, ok := v.(bool); ok { + return strconv.FormatBool(item) + } else if item, ok := v.([]string); ok { + return strings.Join(item, ",") + } else if item, ok := v.([]int64); ok { + log.Debug("interface_to_string: Got []interface {} for ", v) + log.Debug("interface_to_string: ValueOf ", reflect.ValueOf(item)) + val, err := json.Marshal(item) + if err == nil { + return string(val) + } else { + log.Warn("interface_to_string: ", err.Error()) + } + } else if item, ok := v.(map[string]interface{}); ok { + val, err := json.Marshal(item) + if err == nil { + return string(val) + } else { + log.Warn("interface_to_string: ", err.Error()) + } + } else if item, ok := v.([]interface{}); ok { + log.Debug("interface_to_string: Got []interface {} for ", v) + log.Debug("interface_to_string: ValueOf ", reflect.ValueOf(item)) + val, err := json.Marshal(item) + if err == nil { + return string(val) + } else { + log.Warn("interface_to_string: ", err.Error()) + } + } else { + log.Warn("interface_to_string: Failed to process: ", v) + } + + return "" +} diff --git a/go-packages/meep-sss-mgr/onem2m-mgr_test.go b/go-packages/meep-sss-mgr/onem2m-mgr_test.go index e3b43d099c1ca495f0b170e9c7d95d53541f552e..254dfdcc278990be21060e04263b4b9ebeb2f65e 100644 --- a/go-packages/meep-sss-mgr/onem2m-mgr_test.go +++ b/go-packages/meep-sss-mgr/onem2m-mgr_test.go @@ -20,7 +20,7 @@ import ( "fmt" "reflect" "testing" - "time" + //"time" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" ) @@ -34,15 +34,15 @@ const tmNamespace = "sandboxtest" // // Invalid Connector // fmt.Println("Invalid SSS Asset Manager") -// tm, err := NewSssMgr("", tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr("", tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err == nil || tm != nil { // t.Fatalf("Service name not set") // } -// tm, err = NewSssMgr(tmName, tmNamespace, "", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err = NewSssMgr(tmName, tmNamespace, "", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err == nil || tm != nil { // t.Fatalf("Binding protocol not set") // } -// tm, err = NewSssMgr(tmName, tmNamespace, "MQTT", "", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err = NewSssMgr(tmName, tmNamespace, "MQTT", "", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err == nil || tm != nil { // t.Fatalf("Host not set") // } @@ -50,14 +50,14 @@ const tmNamespace = "sandboxtest" // if err == nil || tm != nil { // t.Fatalf("Host id not set") // } -// tm, err = NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "", nil, nil, nil) +// tm, err = NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "", nil, nil, nil) // if err == nil || tm != nil { // t.Fatalf("CSE name not set") // } // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err = NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err = NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -69,7 +69,7 @@ const tmNamespace = "sandboxtest" // tm = nil // fmt.Println("Create valid SSS Asset Manager") -// tm, err = NewSssMgr(tmName, tmNamespace, "REST_HTTP", "172.29.10.56", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err = NewSssMgr(tmName, tmNamespace, "REST_HTTP", "172.29.10.56", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -87,7 +87,7 @@ const tmNamespace = "sandboxtest" // log.MeepTextLogInit(t.Name()) // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -110,7 +110,7 @@ const tmNamespace = "sandboxtest" // log.MeepTextLogInit(t.Name()) // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "", 0, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "", 0, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -136,7 +136,7 @@ const tmNamespace = "sandboxtest" // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -170,7 +170,7 @@ const tmNamespace = "sandboxtest" // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -179,7 +179,7 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "12345", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // new_sensor, err := tm.OneM2M_create(sensor, "") // if err != nil { @@ -191,7 +191,7 @@ const tmNamespace = "sandboxtest" // t.Fatalf("Failed to validate AE content") // } -// _ = tm.OneM2M_Delete(new_sensor) +// _ = tm.OneM2M_delete(new_sensor) // // Cleanup // err = tm.DeleteSssMgr() @@ -207,7 +207,7 @@ const tmNamespace = "sandboxtest" // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -216,7 +216,7 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "CMyAE", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // new_sensor_ae, err := tm.OneM2M_create(sensor_ae, "") // if err != nil { @@ -232,7 +232,7 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "CMyCNT", // SensorType: "CNT", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // // sensor_cnt.SensorCharacteristicList = make([]SensorCharacteristic, 1) // // sensor_cnt.SensorCharacteristicList[0] = SensorCharacteristic{CharacteristicName: "con", CharacteristicValue: "OFF"} @@ -244,14 +244,14 @@ const tmNamespace = "sandboxtest" // // Verify content // if !validate_sensor_cnt(sensor_cnt, new_sensor_cnt) { -// t.Fatalf("Failed to validate AE content") +// t.Fatalf("Failed to validate CNT content") // } -// err = tm.OneM2M_Delete(new_sensor_cnt) +// err = tm.OneM2M_delete(new_sensor_cnt) // if err != nil { // t.Fatalf("Failed to create new sensor") // } -// err = tm.OneM2M_Delete(new_sensor_ae) +// err = tm.OneM2M_delete(new_sensor_ae) // if err != nil { // t.Fatalf("Failed to create new sensor") // } @@ -264,13 +264,87 @@ const tmNamespace = "sandboxtest" // tm = nil // } +func TestOneM2M_createAE_FLEXHttp(t *testing.T) { + fmt.Println("--- ", t.Name()) + log.MeepTextLogInit(t.Name()) + + // Valid Connector + fmt.Println("Create valid SSS Asset Manager") + tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) + if err != nil || tm == nil { + t.Fatalf("Failed to create SSS Asset Manager") + } + + var sensor_ae = SensorDiscoveryInfo{ + SensorIdentifier: "CMyAE", + SensorType: "AE", + SensorPosition: nil, + IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", + } + new_sensor_ae, err := tm.OneM2M_create(sensor_ae, "") + if err != nil { + t.Fatalf("Failed to create new AE sensor") + } + + // Verify content + if !validate_sensor_ae(sensor_ae, new_sensor_ae) { + t.Fatalf("Failed to validate AE content") + } + + var sensor_flex = SensorDiscoveryInfo{ + SensorIdentifier: "CMyFLX", + SensorType: "FLX", + SensorPosition: nil, + IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", + } + sensor_flex.Flex = make(map[string]interface{}, 0) + sensor_flex.Flex["type"] = "cod:color" + sensor_flex.Flex["cnd"] = "org.onem2m.common.moduleclass.colour" + sensor_flex.Flex["red"] = 20 + sensor_flex.Flex["green"] = 20 + sensor_flex.Flex["blue"] = 20 + sensorPath := new_sensor_ae.SensorIdentifier + new_sensor_flex, err := tm.OneM2M_create(sensor_flex, sensorPath) + if err != nil { + t.Fatalf("Failed to create new CNT sensor") + } + + // Verify content + if !validate_sensor_flex(sensor_flex, new_sensor_flex) { + t.Fatalf("Failed to validate FLEX content") + } + + sensors, err := tm.SensorDiscoveryInfoAll() + if err != nil { + t.Fatalf(err.Error()) + } + fmt.Println("len=", len(sensors)) + fmt.Println("sensors", sensors) + + err = tm.OneM2M_delete(new_sensor_flex) + if err != nil { + t.Fatalf("Failed to create new sensor") + } + err = tm.OneM2M_delete(new_sensor_ae) + if err != nil { + t.Fatalf("Failed to create new sensor") + } + + // Cleanup + err = tm.DeleteSssMgr() + if err != nil { + t.Fatalf("Failed to cleanup SSS Asset Manager") + } + tm = nil +} + // func TestOneM2M_createAE_CNT_CNIHttp(t *testing.T) { // fmt.Println("--- ", t.Name()) // log.MeepTextLogInit(t.Name()) // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -279,7 +353,7 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "CMyAE", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // new_sensor_ae, err := tm.OneM2M_create(sensor_ae, "") // if err != nil { @@ -295,7 +369,7 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "CMyCNT", // SensorType: "CNT", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // sensorPath := new_sensor_ae.SensorIdentifier // new_sensor_cnt, err := tm.OneM2M_create(sensor_cnt, sensorPath) @@ -312,7 +386,7 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "CMyCNI", // SensorType: "CIN", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // sensor_cin.SensorCharacteristicList = make([]SensorCharacteristic, 1) // sensor_cin.SensorCharacteristicList[0] = SensorCharacteristic{CharacteristicName: "con", CharacteristicValue: "OFF"} @@ -327,15 +401,15 @@ const tmNamespace = "sandboxtest" // t.Fatalf("Failed to validate CIN content") // } -// err = tm.OneM2M_Delete(new_sensor_cin) +// err = tm.OneM2M_delete(new_sensor_cin) // if err != nil { // t.Fatalf("Failed to create new sensor") // } -// err = tm.OneM2M_Delete(new_sensor_cnt) +// err = tm.OneM2M_delete(new_sensor_cnt) // if err != nil { // t.Fatalf("Failed to create new sensor") // } -// err = tm.OneM2M_Delete(new_sensor_ae) +// err = tm.OneM2M_delete(new_sensor_ae) // if err != nil { // t.Fatalf("Failed to create new sensor") // } @@ -354,7 +428,7 @@ const tmNamespace = "sandboxtest" // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -363,14 +437,14 @@ const tmNamespace = "sandboxtest" // SensorIdentifier: "12345", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // sensor, err := oneM2M_create(tm, new_sensor, "") // if err != nil { // t.Fatalf("Failed to create new sensor: " + err.Error()) // } -// err = tm.OneM2M_Delete(sensor) +// err = tm.OneM2M_delete(sensor) // if err != nil { // t.Fatalf("Failed to create new sensor: " + err.Error()) // } @@ -389,7 +463,7 @@ const tmNamespace = "sandboxtest" // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -423,9 +497,9 @@ const tmNamespace = "sandboxtest" // t.Fatalf("Failed to validate CIN content") // } -// _ = tm.OneM2M_Delete(received_sensors["CIN"]) -// _ = tm.OneM2M_Delete(received_sensors["CNT"]) -// _ = tm.OneM2M_Delete(received_sensors["AE"]) +// _ = tm.OneM2M_delete(received_sensors["CIN"]) +// _ = tm.OneM2M_delete(received_sensors["CNT"]) +// _ = tm.OneM2M_delete(received_sensors["AE"]) // // Cleanup // err = tm.DeleteSssMgr() @@ -441,7 +515,7 @@ const tmNamespace = "sandboxtest" // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -482,9 +556,9 @@ const tmNamespace = "sandboxtest" // t.Fatalf("Failed to validate AE content") // } -// _ = tm.OneM2M_Delete(received_sensors["CIN"]) -// _ = tm.OneM2M_Delete(received_sensors["CNT"]) -// _ = tm.OneM2M_Delete(received_sensors["AE"]) +// _ = tm.OneM2M_delete(received_sensors["CIN"]) +// _ = tm.OneM2M_delete(received_sensors["CNT"]) +// _ = tm.OneM2M_delete(received_sensors["AE"]) // // Cleanup // err = tm.DeleteSssMgr() @@ -494,67 +568,67 @@ const tmNamespace = "sandboxtest" // tm = nil // } -func TestOneM2M_subscribeHttp(t *testing.T) { - fmt.Println("--- ", t.Name()) - log.MeepTextLogInit(t.Name()) +// func TestOneM2M_subscribeHttp(t *testing.T) { +// fmt.Println("--- ", t.Name()) +// log.MeepTextLogInit(t.Name()) - // Valid Connector - fmt.Println("Create valid SSS Asset Manager") - tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "7feaadbb0400", "laboai-acme-ic-cse", discoveryNotify, statusNotify, dataNotify) - if err != nil || tm == nil { - t.Fatalf("Failed to create SSS Asset Manager") - } +// // Valid Connector +// fmt.Println("Create valid SSS Asset Manager") +// tm, err := NewSssMgr(tmName, tmNamespace, "REST_HTTP", "lab-oai.etsi.org", 31110, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", discoveryNotify, statusNotify, dataNotify) +// if err != nil || tm == nil { +// t.Fatalf("Failed to create SSS Asset Manager") +// } - _, received_sensors, err := oneM2M_createAE_CNT_CIN(tm) - if err != nil { - t.Fatalf("Failed to create sensors") - } +// _, received_sensors, err := oneM2M_createAE_CNT_CIN(tm) +// if err != nil { +// t.Fatalf("Failed to create sensors") +// } - subscriptionMap := make(map[string]interface{}) - subId, err := tm.OneM2M_subscribe(received_sensors["AE"].IotPlatformId, received_sensors["AE"].SensorIdentifier) - if err != nil { - t.Fatalf("Failed to subscribe") - } - fmt.Println("subId=" + subId) - subscriptionMap[subId] = received_sensors["AE"] +// subscriptionMap := make(map[string]interface{}) +// subId, err := tm.OneM2M_subscribe(received_sensors["AE"].IotPlatformId, received_sensors["AE"].SensorIdentifier) +// if err != nil { +// t.Fatalf("Failed to subscribe") +// } +// fmt.Println("subId=" + subId) +// subscriptionMap[subId] = received_sensors["AE"] - subId, err = tm.OneM2M_subscribe(received_sensors["CNT"].IotPlatformId, received_sensors["CNT"].SensorIdentifier) - if err != nil { - t.Fatalf("Failed to subscribe") - } - fmt.Println("subId=" + subId) - subscriptionMap[subId] = received_sensors["CNT"] +// subId, err = tm.OneM2M_subscribe(received_sensors["CNT"].IotPlatformId, received_sensors["CNT"].SensorIdentifier) +// if err != nil { +// t.Fatalf("Failed to subscribe") +// } +// fmt.Println("subId=" + subId) +// subscriptionMap[subId] = received_sensors["CNT"] - fmt.Println("len(subscriptionMap)=" + fmt.Sprint(len(subscriptionMap))) +// fmt.Println("len(subscriptionMap)=" + fmt.Sprint(len(subscriptionMap))) - fmt.Println("You have 120 seconds to trigger subscriptions") - time.Sleep(time.Duration(120) * time.Second) +// fmt.Println("You have 120 seconds to trigger subscriptions") +// time.Sleep(time.Duration(120) * time.Second) - for k := range subscriptionMap { - err = tm.OneM2M_DeleteSub(k) - if err != nil { - t.Fatalf("Failed to cancel subscription") - } - } // End of 'for' statement +// for k := range subscriptionMap { +// err = tm.OneM2M_delete_subscription(k) +// if err != nil { +// t.Fatalf("Failed to cancel subscription") +// } +// } // End of 'for' statement - _ = tm.OneM2M_Delete(received_sensors["CIN"]) - _ = tm.OneM2M_Delete(received_sensors["CNT"]) - _ = tm.OneM2M_Delete(received_sensors["AE"]) +// _ = tm.OneM2M_delete(received_sensors["CIN"]) +// _ = tm.OneM2M_delete(received_sensors["CNT"]) +// _ = tm.OneM2M_delete(received_sensors["AE"]) - // Cleanup - err = tm.DeleteSssMgr() - if err != nil { - t.Fatalf("Failed to cleanup SSS Asset Manager") - } - tm = nil -} +// // Cleanup +// err = tm.DeleteSssMgr() +// if err != nil { +// t.Fatalf("Failed to cleanup SSS Asset Manager") +// } +// tm = nil +// } // func TestPopulateDevicesPerIotPlatformsMqtt(t *testing.T) { // fmt.Println("--- ", t.Name()) // log.MeepTextLogInit(t.Name()) // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -576,7 +650,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -605,7 +679,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -639,7 +713,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -648,7 +722,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "12345", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // new_sensor, err := tm.OneM2M_create(sensor, "") // if err != nil { @@ -660,7 +734,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // t.Fatalf("Failed to validate AE content") // } -// _ = tm.OneM2M_Delete(new_sensor) +// _ = tm.OneM2M_delete(new_sensor) // // Cleanup // err = tm.DeleteSssMgr() @@ -676,7 +750,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -685,7 +759,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "CMyAE", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // new_sensor_ae, err := tm.OneM2M_create(sensor_ae, "") // if err != nil { @@ -701,7 +775,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "CMyCNT", // SensorType: "CNT", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // // sensor_cnt.SensorCharacteristicList = make([]SensorCharacteristic, 1) // // sensor_cnt.SensorCharacteristicList[0] = SensorCharacteristic{CharacteristicName: "con", CharacteristicValue: "OFF"} @@ -716,11 +790,11 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // t.Fatalf("Failed to validate CNT content") // } -// err = tm.OneM2M_Delete(new_sensor_cnt) +// err = tm.OneM2M_delete(new_sensor_cnt) // if err != nil { // t.Fatalf("Failed to create new sensor") // } -// err = tm.OneM2M_Delete(new_sensor_ae) +// err = tm.OneM2M_delete(new_sensor_ae) // if err != nil { // t.Fatalf("Failed to create new sensor") // } @@ -739,7 +813,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -748,7 +822,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "CMyAE", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // new_sensor_ae, err := tm.OneM2M_create(sensor_ae, "") // if err != nil { @@ -764,7 +838,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "CMyCNT", // SensorType: "CNT", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // sensorPath := new_sensor_ae.SensorIdentifier // new_sensor_cnt, err := tm.OneM2M_create(sensor_cnt, sensorPath) @@ -781,7 +855,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "CMyCNI", // SensorType: "CIN", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // sensor_cin.SensorCharacteristicList = make([]SensorCharacteristic, 1) // sensor_cin.SensorCharacteristicList[0] = SensorCharacteristic{CharacteristicName: "con", CharacteristicValue: "OFF"} @@ -800,15 +874,15 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // t.Fatalf("received_sensor.SensorCharacteristicList shall not be empty") // } -// err = tm.OneM2M_Delete(new_sensor_cin) +// err = tm.OneM2M_delete(new_sensor_cin) // if err != nil { // t.Fatalf("Failed to create new sensor") // } -// err = tm.OneM2M_Delete(new_sensor_cnt) +// err = tm.OneM2M_delete(new_sensor_cnt) // if err != nil { // t.Fatalf("Failed to create new sensor") // } -// err = tm.OneM2M_Delete(new_sensor_ae) +// err = tm.OneM2M_delete(new_sensor_ae) // if err != nil { // t.Fatalf("Failed to create new sensor") // } @@ -827,7 +901,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -836,14 +910,14 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // SensorIdentifier: "12345", // SensorType: "AE", // SensorPosition: nil, -// IotPlatformId: "7feaadbb0400", +// IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", // } // sensor, err := oneM2M_create(tm, new_sensor, "") // if err != nil { // t.Fatalf("Failed to create new sensor: " + err.Error()) // } -// err = tm.OneM2M_Delete(sensor) +// err = tm.OneM2M_delete(sensor) // if err != nil { // t.Fatalf("Failed to create new sensor: " + err.Error()) // } @@ -862,7 +936,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -896,9 +970,9 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // t.Fatalf("Failed to validate CIN content") // } -// _ = tm.OneM2M_Delete(received_sensors["CIN"]) -// _ = tm.OneM2M_Delete(received_sensors["CNT"]) -// _ = tm.OneM2M_Delete(received_sensors["AE"]) +// _ = tm.OneM2M_delete(received_sensors["CIN"]) +// _ = tm.OneM2M_delete(received_sensors["CNT"]) +// _ = tm.OneM2M_delete(received_sensors["AE"]) // // Cleanup // err = tm.DeleteSssMgr() @@ -914,7 +988,7 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", nil, nil, nil) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } @@ -955,9 +1029,9 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // t.Fatalf("Failed to validate AE content") // } -// _ = tm.OneM2M_Delete(received_sensors["CIN"]) -// _ = tm.OneM2M_Delete(received_sensors["CNT"]) -// _ = tm.OneM2M_Delete(received_sensors["AE"]) +// _ = tm.OneM2M_delete(received_sensors["CIN"]) +// _ = tm.OneM2M_delete(received_sensors["CNT"]) +// _ = tm.OneM2M_delete(received_sensors["AE"]) // // Cleanup // err = tm.DeleteSssMgr() @@ -966,26 +1040,59 @@ func TestOneM2M_subscribeHttp(t *testing.T) { // } // tm = nil // } - -// func TestVaidateOneM2MNotificationServer(t *testing.T) { +// func TestOneM2M_subscribeMQTT(t *testing.T) { // fmt.Println("--- ", t.Name()) // log.MeepTextLogInit(t.Name()) // // Valid Connector // fmt.Println("Create valid SSS Asset Manager") -// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "MQTT", "172.29.10.56", 1883, "7feaadbb0400", "laboai-acme-ic-cse", nil, nil, nil) +// tm, err := NewSssMgr(tmName, tmNamespace, "MQTT", "172.29.10.56", 1883, "0a692154-0f93-11f0-b554-df2f6a756f6a", "laboai-acme-ic-cse", discoveryNotify, statusNotify, dataNotify) // if err != nil || tm == nil { // t.Fatalf("Failed to create SSS Asset Manager") // } -// tm.init() -// fmt.Println("Waiting for 2 minutes to do curl request: curl -v http://mec-platform.etsi.org:31122/sbxykqjr17/mep1/sens/v1 ") +// _, received_sensors, err := oneM2M_createAE_CNT_CIN(tm) +// if err != nil { +// t.Fatalf("Failed to create sensors") +// } + +// subscriptionMap := make(map[string]interface{}) +// subId, err := tm.OneM2M_subscribe(received_sensors["AE"].IotPlatformId, received_sensors["AE"].SensorIdentifier) +// if err != nil { +// t.Fatalf("Failed to subscribe") +// } +// fmt.Println("subId=" + subId) +// subscriptionMap[subId] = received_sensors["AE"] + +// subId, err = tm.OneM2M_subscribe(received_sensors["CNT"].IotPlatformId, received_sensors["CNT"].SensorIdentifier) +// if err != nil { +// t.Fatalf("Failed to subscribe") +// } +// fmt.Println("subId=" + subId) +// subscriptionMap[subId] = received_sensors["CNT"] + +// fmt.Println("len(subscriptionMap)=" + fmt.Sprint(len(subscriptionMap))) + +// fmt.Println("You have 120 seconds to trigger subscriptions") +// time.Sleep(time.Duration(120) * time.Second) + +// for k := range subscriptionMap { +// err = tm.OneM2M_delete_subscription(k) +// if err != nil { +// t.Fatalf("Failed to cancel subscription") +// } +// } // End of 'for' statement + +// _ = tm.OneM2M_delete(received_sensors["CIN"]) +// _ = tm.OneM2M_delete(received_sensors["CNT"]) +// _ = tm.OneM2M_delete(received_sensors["AE"]) // // Cleanup // err = tm.DeleteSssMgr() // if err != nil { // t.Fatalf("Failed to cleanup SSS Asset Manager") // } +// tm = nil // } func oneM2M_create(tm *SssMgr, sensor SensorDiscoveryInfo, path string) (sensorResp SensorDiscoveryInfo, err error) { @@ -1005,7 +1112,7 @@ func oneM2M_createAE_CNT_CIN(tm *SssMgr) (sensors map[string]SensorDiscoveryInfo SensorIdentifier: "CMyAE", SensorType: "AE", SensorPosition: nil, - IotPlatformId: "7feaadbb0400", + IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", } sensors[sensor_ae.SensorType] = sensor_ae new_sensor_ae, err := oneM2M_create(tm, sensor_ae, "") @@ -1018,7 +1125,7 @@ func oneM2M_createAE_CNT_CIN(tm *SssMgr) (sensors map[string]SensorDiscoveryInfo SensorIdentifier: "CMyCNT", SensorType: "CNT", SensorPosition: nil, - IotPlatformId: "7feaadbb0400", + IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", } sensorPath := new_sensor_ae.SensorIdentifier sensors[sensor_cnt.SensorType] = sensor_cnt @@ -1032,7 +1139,7 @@ func oneM2M_createAE_CNT_CIN(tm *SssMgr) (sensors map[string]SensorDiscoveryInfo SensorIdentifier: "CMyCNI", SensorType: "CIN", SensorPosition: nil, - IotPlatformId: "7feaadbb0400", + IotPlatformId: "0a692154-0f93-11f0-b554-df2f6a756f6a", } sensor_cin.SensorCharacteristicList = make([]SensorCharacteristic, 1) sensor_cin.SensorCharacteristicList[0] = SensorCharacteristic{CharacteristicName: "con", CharacteristicValue: "OFF"} @@ -1122,7 +1229,7 @@ func validate_sensor_cnt(expected_sensor SensorDiscoveryInfo, received_sensor Se fmt.Println(">>> validate_sensor_cnt: received_sensor: ", received_sensor) if received_sensor.SensorIdentifier == "" { - fmt.Println("validate_sensor_ae.SensorIdentifier shall be set") + fmt.Println("validate_sensor_cnt.SensorIdentifier shall be set") return false } if received_sensor.SensorType != received_sensor.SensorType { @@ -1207,6 +1314,39 @@ func validate_sensor_cin(expected_sensor SensorDiscoveryInfo, received_sensor Se return true } +func validate_sensor_flex(expected_sensor SensorDiscoveryInfo, received_sensor SensorDiscoveryInfo) bool { + fmt.Println(">>> validate_sensor_flex: expected_sensor: ", expected_sensor) + fmt.Println(">>> validate_sensor_flex: received_sensor: ", received_sensor) + + if received_sensor.SensorIdentifier == "" { + fmt.Println("validate_sensor_flex.SensorIdentifier shall be set") + return false + } + if received_sensor.SensorType != received_sensor.SensorType { + fmt.Println("validate_sensor_flex.SensorType != SensorType") + return false + } + if received_sensor.IotPlatformId != expected_sensor.IotPlatformId { + fmt.Println("validate_sensor_flex.IotPlatformId != IotPlatformId") + return false + } + if expected_sensor.Flex == nil || received_sensor.Flex == nil { + fmt.Println("validate_sensor_flex.Flex != Flex") + return false + } else if len(expected_sensor.Flex) != len(received_sensor.Flex) { + fmt.Println("len(validate_sensor_flex.Flex) != len(Flex)") + return false + } else if expected_sensor.Flex["type"] != received_sensor.Flex["type"] { + fmt.Println("validate_sensor_flex.Flex[type] != Flex[type]") + return false + } else if expected_sensor.Flex["cnd"] != received_sensor.Flex["cnd"] { + fmt.Println("validate_sensor_flex.Flex[cnd] != Flex[cnd]") + return false + } + + return true +} + func discoveryNotify(map[string]interface{}) { log.Debug(">>> discoveryNotify") diff --git a/js-apps/meep-frontend/src/js/containers/cfg/cfg-network-element-container.js b/js-apps/meep-frontend/src/js/containers/cfg/cfg-network-element-container.js index 5ae54fdc25199f6d2d0a4cde50619731efad067f..4ed7e1f3d5058b8ae62292af66132d8a8d5b2b65 100644 --- a/js-apps/meep-frontend/src/js/containers/cfg/cfg-network-element-container.js +++ b/js-apps/meep-frontend/src/js/containers/cfg/cfg-network-element-container.js @@ -1363,33 +1363,41 @@ const TypeRelatedFormFields = ({ onUpdate, onEditLocation, onEditPath, element } const cfgElementTypes = [ { label: 'Logical Domain', - options: [ELEMENT_TYPE_OPERATOR_GENERIC, ELEMENT_TYPE_OPERATOR_CELL] + options: [ + { label: 'Generic', value: ELEMENT_TYPE_OPERATOR_GENERIC }, + { label: 'Cellular', value: ELEMENT_TYPE_OPERATOR_CELL } + ] }, { label: 'Logical Zone', - options: [ELEMENT_TYPE_ZONE] + options: [{ label: 'Zone', value: ELEMENT_TYPE_ZONE }] }, { label: 'Network Location', - options: [ELEMENT_TYPE_POA_GENERIC, ELEMENT_TYPE_POA_4G, ELEMENT_TYPE_POA_5G, ELEMENT_TYPE_POA_WIFI] + options: [ + { label: 'Generic', value: ELEMENT_TYPE_POA_GENERIC }, + { label: '4G', value: ELEMENT_TYPE_POA_4G }, + { label: '5G', value: ELEMENT_TYPE_POA_5G }, + { label: 'WiFi', value: ELEMENT_TYPE_POA_WIFI } + ] }, { label: 'Physical Location', options: [ - ELEMENT_TYPE_UE, - ELEMENT_TYPE_FOG, - ELEMENT_TYPE_EDGE, - ELEMENT_TYPE_DC - // ELEMENT_TYPE_CN + { label: 'Terminal', value: ELEMENT_TYPE_UE }, + { label: 'Fog', value: ELEMENT_TYPE_FOG }, + { label: 'Edge', value: ELEMENT_TYPE_EDGE }, + { label: 'Distant Cloud', value: ELEMENT_TYPE_DC } + // { label: 'Core Network', value: ELEMENT_TYPE_CN } ] }, { label: 'Process', options: [ - ELEMENT_TYPE_UE_APP, - // ELEMENT_TYPE_MECSVC, - ELEMENT_TYPE_EDGE_APP, - ELEMENT_TYPE_CLOUD_APP + { label: 'Terminal Application', value: ELEMENT_TYPE_UE_APP }, + // { label: 'MEC Service', value: ELEMENT_TYPE_MECSVC }, + { label: 'Edge Application', value: ELEMENT_TYPE_EDGE_APP }, + { label: 'Cloud Application', value: ELEMENT_TYPE_CLOUD_APP } ] } ]; @@ -1398,9 +1406,9 @@ const execElementTypes = [ { label: 'Process', options: [ - ELEMENT_TYPE_UE_APP, - ELEMENT_TYPE_EDGE_APP, - ELEMENT_TYPE_CLOUD_APP + { label: 'Terminal Application', value: ELEMENT_TYPE_UE_APP }, + { label: 'Edge Application', value: ELEMENT_TYPE_EDGE_APP }, + { label: 'Cloud Application', value: ELEMENT_TYPE_CLOUD_APP } ] } ]; diff --git a/js-apps/meep-frontend/src/js/containers/cfg/cfg-page-container.js b/js-apps/meep-frontend/src/js/containers/cfg/cfg-page-container.js index 30a19d518d49443025f8f3067855849d7a7bab45..7e4e1b9bca3cbc6ffd50b604482945cb198c0851 100644 --- a/js-apps/meep-frontend/src/js/containers/cfg/cfg-page-container.js +++ b/js-apps/meep-frontend/src/js/containers/cfg/cfg-page-container.js @@ -68,7 +68,19 @@ import { IDC_DIALOG_EXPORT_SCENARIO, ELEMENT_TYPE_POA_4G, ELEMENT_TYPE_POA_5G, - ELEMENT_TYPE_POA_WIFI + ELEMENT_TYPE_POA_WIFI, + ELEMENT_TYPE_UE, + ELEMENT_TYPE_FOG, + ELEMENT_TYPE_EDGE, + ELEMENT_TYPE_DC, + ELEMENT_TYPE_CN, + ELEMENT_TYPE_UE_APP, + ELEMENT_TYPE_EDGE_APP, + ELEMENT_TYPE_CLOUD_APP, + ELEMENT_TYPE_OPERATOR, + ELEMENT_TYPE_OPERATOR_CELL, + ELEMENT_TYPE_ZONE, + ELEMENT_TYPE_POA } from '../../meep-constants'; import { @@ -89,6 +101,7 @@ import { FIELD_MEMORY_MIN, FIELD_MEMORY_MAX } from '../../util/elem-utils'; +import { getElementFromScenario, updateElementInScenario } from '../../util/scenario-utils'; import { pipe, filter } from '../../util/functional'; @@ -137,6 +150,30 @@ export const validateNetworkElement = (element, entries, elemSetErrMsg) => { return false; } + // Check if type is valid + const validTypes = [ + ELEMENT_TYPE_SCENARIO, + ELEMENT_TYPE_OPERATOR, + ELEMENT_TYPE_OPERATOR_CELL, + ELEMENT_TYPE_ZONE, + ELEMENT_TYPE_POA, + ELEMENT_TYPE_POA_4G, + ELEMENT_TYPE_POA_5G, + ELEMENT_TYPE_POA_WIFI, + ELEMENT_TYPE_UE, + ELEMENT_TYPE_FOG, + ELEMENT_TYPE_EDGE, + ELEMENT_TYPE_DC, + ELEMENT_TYPE_CN, + ELEMENT_TYPE_UE_APP, + ELEMENT_TYPE_EDGE_APP, + ELEMENT_TYPE_CLOUD_APP + ]; + if (!validTypes.includes(type)) { + elemSetErrMsg('Invalid element type: ' + type); + return false; + } + // Check for valid & unique network element name (except if editing) var name = getElemFieldVal(element, FIELD_NAME); if (name === null || name === '') { @@ -144,10 +181,10 @@ export const validateNetworkElement = (element, entries, elemSetErrMsg) => { return false; } - if (data[name] && data[name].id !== element.id) { - elemSetErrMsg('Element name already exists'); - return false; - } + // if (data[name] && data[name].id !== element.id) { + // elemSetErrMsg('Element name already exists'); + // return false; + // } // Nothing else to validate for Scenario element if (type === ELEMENT_TYPE_SCENARIO) { @@ -155,9 +192,16 @@ export const validateNetworkElement = (element, entries, elemSetErrMsg) => { } // Make sure parent exists - if (!data[getElemFieldVal(element, FIELD_PARENT)]) { - elemSetErrMsg('Parent does not exist'); - return false; + var parentName = getElemFieldVal(element, FIELD_PARENT); + if (parentName) { + parentName = parentName.trim(); + if (!Object.values(data).some(e => { + const eName = getElemFieldVal(e, FIELD_NAME); + return eName && eName.trim() === parentName; + })) { + elemSetErrMsg('Parent does not exist'); + return false; + } } // If GPU requested, make sure type is set @@ -245,16 +289,16 @@ class CfgPageContainer extends Component { } // EDIT - onEditElement(element) { - if (element !== null) { - if (!this.props.configuredElement || (element.id !== this.props.configuredElement.id)) { - resetElem(element); + onEditElement(element) { + if (element !== null) { + // Always fetch the full element from the scenario by ID + // let element = getElementFromScenario(this.props.cfg.scenario, element.id); + //resetElem(element); this.props.cfgElemEdit(element); + } else { + this.props.cfgElemClear(); } - } else { - this.props.cfgElemClear(); } - } // SAVE onSaveElement(element) { @@ -270,6 +314,11 @@ class CfgPageContainer extends Component { this.props.updateScenarioElem(element); } + // Update scenario for canvas reflection + // let updatedScenario = JSON.parse(JSON.stringify(this.props.cfg.scenario)); + // updateElementInScenario(updatedScenario, element); + // this.props.changeScenario(updatedScenario); + // Reset Element configuration pane this.props.cfgElemClear(); } @@ -288,8 +337,10 @@ class CfgPageContainer extends Component { this.props.cloneScenarioElem(element); - //force update on the visual aspect of the scenario - //this.props.updateScenario(); + // Update scenario for canvas reflection + const updatedScenario = JSON.parse(JSON.stringify(this.props.cfg.scenario)); + updateElementInScenario(updatedScenario, element); + this.props.changeScenario(updatedScenario); this.props.cfgElemClear(); } @@ -467,6 +518,11 @@ class CfgPageContainer extends Component { const scenarioCopy = JSON.parse(JSON.stringify(cfg.scenario)); scenarioCopy.name = scenarioName; + // Update scenario with configured elements + Object.values(cfg.table.entries).forEach(element => { + updateElementInScenario(scenarioCopy, element); + }); + // Create new scenario if scenario is new if (cfg.state === CFG_STATE_NEW || scenarioName !== cfg.scenario.name) { this.props.api.createScenario( @@ -737,7 +793,7 @@ class CfgPageContainer extends Component { type={TYPE_CFG} onNewElement={() => this.onNewElement()} onEditElement={elem => this.onEditElement(elem)} - onDeleteElement={() => this.onDeleteElement()} + onDeleteElement={elem => this.onDeleteElement(elem)} onApplyCloneElement={elem => this.onApplyCloneElement(elem)} /> diff --git a/js-apps/meep-frontend/src/js/containers/cfg/cfg-table.js b/js-apps/meep-frontend/src/js/containers/cfg/cfg-table.js index 6ebb5f1ac96c5eb3a42c14879a402f83391caf28..f000768bf643f65aa6f0973c0d48542faf29d40d 100644 --- a/js-apps/meep-frontend/src/js/containers/cfg/cfg-table.js +++ b/js-apps/meep-frontend/src/js/containers/cfg/cfg-table.js @@ -95,10 +95,15 @@ class CfgTable extends Component { this.props.changeTable(table); } - onClick(/*event, name*/) { - // var table = updateObject({}, this.props.table); - // handleClick(table, event, name); - // this.props.changeTable(table); + onClick(event, elem) { + // Select by unique id + var table = updateObject({}, this.props.table); + table.selected = [elem.id]; + this.props.changeTable(table); + // Call parent handler to open config panel for this element + if (this.props.onEditElement) { + this.props.onEditElement(elem); + } } onChangePage(event, page) { @@ -198,15 +203,16 @@ class CfgTable extends Component { const name = getElemFieldVal(elem, FIELD_NAME); const type = getElemFieldVal(elem, FIELD_TYPE); const parent = getElemFieldVal(elem, FIELD_PARENT); - const isSelected = isRowSelected(table, name); + const id = elem.id; + const isSelected = table.selected && table.selected[0] === id; return ( this.onClick(event, name)} + onClick={event => this.onClick(event, elem)} role='checkbox' aria-checked={isSelected} tabIndex={-1} - key={name} + key={id} selected={isSelected} > diff --git a/js-apps/meep-frontend/src/js/containers/idc-map.js b/js-apps/meep-frontend/src/js/containers/idc-map.js index 07aa0a222c3057b01f2211abea308a6110fcb0db..95d8ff787bcb03d8fd3b0bdcce6f62b062e088e1 100644 --- a/js-apps/meep-frontend/src/js/containers/idc-map.js +++ b/js-apps/meep-frontend/src/js/containers/idc-map.js @@ -167,18 +167,32 @@ class IDCMap extends Component { if (this.props.type === TYPE_CFG) { // Target element update if (nextProps.configuredElement !== this.props.configuredElement) { + console.log('[IDCMap] shouldComponentUpdate: configuredElement changed', nextProps.configuredElement); return true; } // Scenario change if (nextProps.cfgScenarioName !== this.props.cfgScenarioName) { + console.log('[IDCMap] shouldComponentUpdate: cfgScenarioName changed', nextProps.cfgScenarioName); + return true; + } + // Scenario content change + if (!deepEqual(nextProps.cfgScenario, this.props.cfgScenario)) { + console.log('[IDCMap] shouldComponentUpdate: cfgScenario content changed'); + return true; + } + // Table change (needed for zone color lookups) + if (!deepEqual(nextProps.cfgTable, this.props.cfgTable)) { + console.log('[IDCMap] shouldComponentUpdate: cfgTable changed'); return true; } // Sandbox update if (nextProps.cfgView !== this.props.cfgView) { + console.log('[IDCMap] shouldComponentUpdate: cfgView changed', nextProps.cfgView); return true; } // Map asset change if (!deepEqual(this.getMap(nextProps), this.getMap(this.props))) { + console.log('[IDCMap] shouldComponentUpdate: map data changed'); return true; } return false; @@ -200,6 +214,61 @@ class IDCMap extends Component { return (this.props.type === TYPE_CFG) ? this.props.cfgTable : this.props.execTable; } + buildMapFromScenario(scenario) { + var map = { + ueList: [], + poaList: [], + computeList: [] + }; + + if (!scenario || !scenario.deployment || !scenario.deployment.domains) { + return map; + } + + scenario.deployment.domains.forEach(domain => { + if (domain.zones) { + domain.zones.forEach(zone => { + if (zone.networkLocations) { + zone.networkLocations.forEach(nl => { + if (nl.physicalLocations) { + nl.physicalLocations.forEach(pl => { + if (pl.type === 'UE' && pl.geoData && pl.geoData.location) { + map.ueList.push({ + assetName: pl.name, + id: pl.id || pl.name, + radius: pl.geoData.radius || 0, + location: pl.geoData.location, + path: pl.geoData.path || null, + eopMode: pl.geoData.eopMode || null, + velocity: pl.geoData.velocity || null + }); + } else if ((pl.type === 'FOG' || pl.type === 'EDGE' || pl.type === 'DC') && pl.geoData && pl.geoData.location) { + map.computeList.push({ + assetName: pl.name, + id: pl.id || pl.name, + location: pl.geoData.location + }); + } + }); + } + // For POA + if ((nl.type === 'POA' || nl.type === 'POA-4G' || nl.type === 'POA-5G' || nl.type === 'POA-WIFI') && nl.geoData && nl.geoData.location) { + map.poaList.push({ + assetName: nl.name, + id: nl.id || nl.name, + location: nl.geoData.location, + radius: nl.geoData.radius || 0 + }); + } + }); + } + }); + } + }); + + return map; + } + updateCfg(cfg) { switch (this.props.type) { case TYPE_CFG: @@ -230,10 +299,10 @@ class IDCMap extends Component { } } - editElement(name) { + editElement(id) { // Update selected nodes in table const table = updateObject({}, this.getTable()); - const elem = this.getElementByName(table.entries, name); + const elem = this.getElementById(table.entries, id); table.selected = elem ? [elem.id] : []; this.changeTable(table); @@ -241,8 +310,8 @@ class IDCMap extends Component { if (this.props.type === TYPE_CFG) { this.props.onEditElement(elem ? elem : this.props.configuredElement); - // Update target element name & reset controls on target change - if (name !== this.targetElemName) { + // Update target element & reset controls on target change + if (id !== this.targetElemId) { this.map.pm.disableDraw('Marker'); this.map.pm.disableDraw('Line'); if (this.map.pm.globalEditEnabled()) { @@ -255,12 +324,12 @@ class IDCMap extends Component { this.map.pm.toggleGlobalRemovalMode(); } } - this.targetElemName = name; + this.targetElemId = id; } } - getElementByName(entries, name) { - var element = entries[name]; + getElementById(entries, id) { + var element = entries[id]; return element ? element : null; } @@ -401,7 +470,7 @@ class IDCMap extends Component { var radius = 0; var table = this.getTable(); if (table && table.entries) { - radius = getElemFieldVal(table.entries[scenarioName], FIELD_D2D_RADIUS); + radius = getElemFieldVal(table.entries[scenarioName], FIELD_D2D_RADIUS); // scenarioName should be scenario ID if available } return radius; } @@ -416,7 +485,7 @@ class IDCMap extends Component { var poa = null; var table = this.getTable(); if (table && table.entries) { - poa = getElemFieldVal(table.entries[ue], FIELD_PARENT); + poa = getElemFieldVal(table.entries[ue], FIELD_PARENT); // ue should be ue ID } return poa; } @@ -744,12 +813,12 @@ class IDCMap extends Component { setUeMarker(ue) { var latlng = L.latLng(L.GeoJSON.coordsToLatLng(ue.location.coordinates)); - var pathLatLngs = ue.path ? L.GeoJSON.coordsToLatLngs(ue.path.coordinates) : null; + var pathLatLngs = (ue.path && ue.path.coordinates) ? L.GeoJSON.coordsToLatLngs(ue.path.coordinates) : null; // Find existing UE marker var existingMarker; this.ueOverlay.eachLayer((marker) => { - if (marker.options.meep.ue.id === ue.assetName){ + if (marker.options.meep.ue.id === (ue.id || ue.assetName)){ existingMarker = marker; return; } @@ -788,23 +857,24 @@ class IDCMap extends Component { pmIgnore: true }); - var m = L.marker(latlng, { - meep: { - ue: { - id: ue.assetName, - path: p, - eopMode: ue.eopMode, - velocity: ue.velocity, - connected: true, - d2dInRange: ue.d2dInRange, - range: c - } - }, - icon: markerIcon, - opacity: UE_OPACITY, - draggable: (this.props.type === TYPE_CFG) ? true : false, - pmIgnore: (this.props.type === TYPE_CFG) ? false : true - }); + var m = L.marker(latlng, { + meep: { + ue: { + id: ue.id || ue.assetName, + name: ue.assetName, + path: p, + eopMode: ue.eopMode, + velocity: ue.velocity, + connected: true, + d2dInRange: ue.d2dInRange, + range: c + } + }, + icon: markerIcon, + opacity: UE_OPACITY, + draggable: (this.props.type === TYPE_CFG && this.map.pm.globalDragModeEnabled()) ? true : false, + pmIgnore: (this.props.type === TYPE_CFG) ? false : true + }); m.bindTooltip(ue.assetName).openTooltip(); // Handlers @@ -884,7 +954,7 @@ class IDCMap extends Component { // Find existing POA marker var existingMarker; this.poaOverlay.eachLayer((marker) => { - if (marker.options.meep.poa.id === poa.assetName){ + if (marker.options.meep.poa.id === (poa.id || poa.assetName)){ existingMarker = marker; return; } @@ -915,7 +985,8 @@ class IDCMap extends Component { var m = L.marker(latlng, { meep: { poa: { - id: poa.assetName, + id: poa.id || poa.assetName, + name: poa.assetName, range: c } }, @@ -965,7 +1036,7 @@ class IDCMap extends Component { // Find existing COMPUTE marker var existingMarker; this.computeOverlay.eachLayer((marker) => { - if (marker.options.meep.compute.id === compute.assetName){ + if (marker.options.meep.compute.id === (compute.id || compute.assetName)){ existingMarker = marker; return; } @@ -985,7 +1056,8 @@ class IDCMap extends Component { var m = L.marker(latlng, { meep: { compute: { - id: compute.assetName, + id: compute.id || compute.assetName, + name: compute.assetName, connected: true } }, @@ -1109,7 +1181,13 @@ class IDCMap extends Component { } // Get copy of map data - var map = deepCopy(this.getMap(this.props)); + var map; + if (this.props.type === TYPE_CFG) { + map = this.buildMapFromScenario(this.props.cfgScenario); + console.log('[IDCMap] updateMarkers (CFG): ues=', map.ueList.length, 'poas=', map.poaList.length, 'computes=', map.computeList.length); + } else { + map = deepCopy(this.getMap(this.props)); + } if (!map) { return; } @@ -1125,7 +1203,7 @@ class IDCMap extends Component { for (let i = 0; i < map.computeList.length; i++) { let compute = map.computeList[i]; this.setComputeMarker(compute); - computeMap[compute.assetName] = true; + computeMap[compute.id || compute.assetName] = true; } } @@ -1142,7 +1220,7 @@ class IDCMap extends Component { for (let i = 0; i < map.poaList.length; i++) { let poa = map.poaList[i]; this.setPoaMarker(poa); - poaMap[poa.assetName] = true; + poaMap[poa.id || poa.assetName] = true; } } @@ -1160,7 +1238,7 @@ class IDCMap extends Component { for (let i = 0; i < map.ueList.length; i++) { let ue = map.ueList[i]; this.setUeMarker(ue); - ueMap[ue.assetName] = true; + ueMap[ue.id || ue.assetName] = true; } } @@ -1177,20 +1255,20 @@ class IDCMap extends Component { } onEditModeToggle(e) { - var targetElemName = getElemFieldVal(this.props.configuredElement, FIELD_NAME); + var targetElemId = this.props.configuredElement ? this.props.configuredElement.id : null; if (e.enabled) { - this.setTarget(targetElemName); + this.setTarget(targetElemId); } else { - this.updateTargetGeoData(targetElemName, '', ''); + this.updateTargetGeoData(targetElemId, '', ''); } } onDragModeToggle(e) { - var targetElemName = getElemFieldVal(this.props.configuredElement, FIELD_NAME); + var targetElemId = this.props.configuredElement ? this.props.configuredElement.id : null; if (e.enabled) { - this.setTarget(targetElemName); + this.setTarget(targetElemId); } else { - this.updateTargetGeoData(targetElemName, '', ''); + this.updateTargetGeoData(targetElemId, '', ''); } } @@ -1210,8 +1288,8 @@ class IDCMap extends Component { } // Update configured element & refresh map to create the new marker or path - var targetElemName = getElemFieldVal(this.props.configuredElement, FIELD_NAME); - this.updateTargetGeoData(targetElemName, location, path); + var targetElemId = this.props.configuredElement ? this.props.configuredElement.id : null; + this.updateTargetGeoData(targetElemId, location, path); } onPoaMoved(e) { @@ -1225,14 +1303,14 @@ class IDCMap extends Component { this.props.cfgElemUpdate(updatedElem); } - updateTargetGeoData(targetElemName, location, path) { - if (!targetElemName) { + updateTargetGeoData(targetElemId, location, path) { + if (!targetElemId) { return; } // Get latest geoData from map, if any if (!location) { - var markerInfo = this.getMarkerInfo(targetElemName); + var markerInfo = this.getMarkerInfo(targetElemId); if (markerInfo && markerInfo.marker) { location = JSON.stringify(L.GeoJSON.latLngToCoords(markerInfo.marker.getLatLng())); if (!path && markerInfo.type === TYPE_UE && markerInfo.marker.options.meep.ue.path) { @@ -1268,6 +1346,9 @@ class IDCMap extends Component { setTarget(target) { // Disable changes on all markers except target + console.log('[IDCMap] setTarget:', target); + var isDragModeEnabled = this.map.pm.globalDragModeEnabled(); + this.ueOverlay.eachLayer((marker) => { var path = marker.options.meep.ue.path; if (marker.pm && (!target || marker.options.meep.ue.id !== target)) { @@ -1279,8 +1360,14 @@ class IDCMap extends Component { } } else { marker.setOpacity(OPACITY_TARGET); + if (marker.pm && isDragModeEnabled) { + marker.pm.enable(); + } if (path) { path.setStyle({opacity: OPACITY_TARGET}); + if (path.pm && this.map.pm.globalEditEnabled()) { + path.pm.enable(); + } } } }); @@ -1291,6 +1378,9 @@ class IDCMap extends Component { marker.options.meep.poa.range.setStyle({opacity: target ? POA_RANGE_OPACITY_BACKGROUND : POA_RANGE_OPACITY}); } else { marker.setOpacity(OPACITY_TARGET); + if (marker.pm && isDragModeEnabled) { + marker.pm.enable(); + } marker.options.meep.poa.range.setStyle({opacity: OPACITY_TARGET}); } }); @@ -1300,6 +1390,9 @@ class IDCMap extends Component { marker.setOpacity(target ? COMPUTE_OPACITY_BACKGROUND : COMPUTE_OPACITY); } else { marker.setOpacity(OPACITY_TARGET); + if (marker.pm && isDragModeEnabled) { + marker.pm.enable(); + } } }); } @@ -1316,11 +1409,16 @@ class IDCMap extends Component { var removalModeEnabled = false; // Update target element name & reset controls on target change - var targetElemName = getElemFieldVal(this.props.configuredElement, FIELD_NAME); + var targetElemId = this.props.configuredElement ? this.props.configuredElement.id : null; + console.log('[IDCMap] updateEditControls: targetElemId =', targetElemId); // Determine which controls to enable - if (targetElemName) { - var markerInfo = this.getMarkerInfo(targetElemName); + // Only look up a marker if we have a target id; treat a stale/unmatched id the + // same as "no target" so all markers remain interactive instead of being dimmed. + var markerInfo = targetElemId ? this.getMarkerInfo(targetElemId) : null; + var markerTarget = (markerInfo && markerInfo.marker) ? targetElemId : null; + + if (targetElemId) { if (markerInfo && markerInfo.marker) { // Enable path create/edit for UE only if (markerInfo.type === TYPE_UE) { @@ -1330,10 +1428,12 @@ class IDCMap extends Component { editModeEnabled = true; } dragModeEnabled = true; + console.log('[IDCMap] updateEditControls: found marker for target, dragMode =', dragModeEnabled); // removalModeEnabled = true; } else { - // Enable marker creation + // Element is configured but has no map marker yet — enable placement drawMarkerEnabled = true; + console.log('[IDCMap] updateEditControls: no marker for target (unplaced/stale elem), drawMarker enabled'); } } @@ -1364,8 +1464,11 @@ class IDCMap extends Component { } } - // Set target element & disable edit on all other markers - this.setTarget(targetElemName); + // Only focus (dim others) when the configured element actually has a marker on + // the map. If it doesn't (element not yet placed, or stale id from a previous + // scenario), pass null so all markers remain fully interactive. + console.log('[IDCMap] updateEditControls: setTarget with', markerTarget); + this.setTarget(markerTarget); } render() { @@ -1393,6 +1496,7 @@ const mapStateToProps = state => { configuredElement: state.cfg.elementConfiguration.configuredElement, cfgView: state.ui.cfgView, cfgScenarioName: state.cfg.scenario.name, + cfgScenario: state.cfg.scenario, appInstanceTable: state.exec.appInstanceTable.data }; }; diff --git a/js-apps/meep-frontend/src/js/meep-constants.js b/js-apps/meep-frontend/src/js/meep-constants.js index 974cd7e3c7af76e229e31704cd832a6bf2707267..a8d086e6c3ff1ca91c291e4d8a6512d3b3995cd8 100644 --- a/js-apps/meep-frontend/src/js/meep-constants.js +++ b/js-apps/meep-frontend/src/js/meep-constants.js @@ -262,23 +262,23 @@ export const CLOUD_APP_TYPE_STR = 'CLOUD-APP'; export const ELEMENT_TYPE_SCENARIO = 'SCENARIO'; export const ELEMENT_TYPE_OPERATOR = 'OPERATOR'; -export const ELEMENT_TYPE_OPERATOR_GENERIC = 'OPERATOR GENERIC'; -export const ELEMENT_TYPE_OPERATOR_CELL = 'OPERATOR CELLULAR'; +export const ELEMENT_TYPE_OPERATOR_GENERIC = 'OPERATOR'; +export const ELEMENT_TYPE_OPERATOR_CELL = 'OPERATOR-CELLULAR'; export const ELEMENT_TYPE_ZONE = 'ZONE'; export const ELEMENT_TYPE_POA = 'POA'; -export const ELEMENT_TYPE_POA_GENERIC = 'POA GENERIC'; -export const ELEMENT_TYPE_POA_4G = 'POA CELLULAR 4G'; -export const ELEMENT_TYPE_POA_5G = 'POA CELLULAR 5G'; -export const ELEMENT_TYPE_POA_WIFI = 'POA WIFI'; -export const ELEMENT_TYPE_DC = 'DISTANT CLOUD'; -export const ELEMENT_TYPE_CN = 'CORE NETWORK'; +export const ELEMENT_TYPE_POA_GENERIC = 'POA'; +export const ELEMENT_TYPE_POA_4G = 'POA-4G'; +export const ELEMENT_TYPE_POA_5G = 'POA-5G'; +export const ELEMENT_TYPE_POA_WIFI = 'POA-WIFI'; +export const ELEMENT_TYPE_DC = 'DC'; +export const ELEMENT_TYPE_CN = 'CN'; export const ELEMENT_TYPE_EDGE = 'EDGE'; export const ELEMENT_TYPE_FOG = 'FOG'; -export const ELEMENT_TYPE_UE = 'TERMINAL'; -export const ELEMENT_TYPE_MECSVC = 'MEC SERVICE'; -export const ELEMENT_TYPE_UE_APP = 'TERMINAL APPLICATION'; -export const ELEMENT_TYPE_EDGE_APP = 'EDGE APPLICATION'; -export const ELEMENT_TYPE_CLOUD_APP = 'CLOUD APPLICATION'; +export const ELEMENT_TYPE_UE = 'UE'; +export const ELEMENT_TYPE_MECSVC = 'MEC-SVC'; +export const ELEMENT_TYPE_UE_APP = 'UE-APP'; +export const ELEMENT_TYPE_EDGE_APP = 'EDGE-APP'; +export const ELEMENT_TYPE_CLOUD_APP = 'CLOUD-APP'; // Default latencies per physical location type export const DEFAULT_LATENCY_INTER_DOMAIN = 50; diff --git a/js-apps/meep-frontend/src/js/state/cfg/table-reducer.js b/js-apps/meep-frontend/src/js/state/cfg/table-reducer.js index 3856315b6435130f3519281329e51e70de47fd11..61ae03a88b8040aef34b43852577a47f5dd82a9e 100644 --- a/js-apps/meep-frontend/src/js/state/cfg/table-reducer.js +++ b/js-apps/meep-frontend/src/js/state/cfg/table-reducer.js @@ -38,7 +38,7 @@ export function cfgChangeTable(table) { export function cfgTableReducer(state = initialState, action) { switch (action.type) { case CFG_CHANGE_TABLE: - var ret = updateObject({}, action.payload); + var ret = updateObject(state, action.payload); return ret; default: return state; diff --git a/js-apps/meep-frontend/src/js/util/scenario-utils.js b/js-apps/meep-frontend/src/js/util/scenario-utils.js index 896921fd7e031b83074047a6ca37c9c018d652ad..8bbd72f8eec68ee239d43066a9e9d510c65d2bbe 100644 --- a/js-apps/meep-frontend/src/js/util/scenario-utils.js +++ b/js-apps/meep-frontend/src/js/util/scenario-utils.js @@ -298,7 +298,7 @@ export function parseScenario(scenario, pduSessions) { var table = {}; table.data = { edges: edges, nodes: nodes }; table.entries = _.reduce(table.data.nodes, (nodeMap, node) => { - nodeMap[node.name] = updateObject(node, getElementFromScenario(scenario, node.id)); + nodeMap[node.id] = updateObject(node, getElementFromScenario(scenario, node.id)); return nodeMap; }, {}); @@ -306,7 +306,7 @@ export function parseScenario(scenario, pduSessions) { var visData = {}; visData.nodes = new visdata.DataSet(nodes); visData.edges = new visdata.DataSet(edges); - + // Update map data var mapData = {}; mapData.ueList = _.sortBy(ueList, ['assetName']); @@ -569,6 +569,8 @@ export function updateElementInScenario(scenario, element) { } domain.label = name; domain.name = name; + domain.type = getElemFieldVal(element, FIELD_TYPE); + domain.parent = getElemFieldVal(element, FIELD_PARENT); return; } @@ -601,6 +603,8 @@ export function updateElementInScenario(scenario, element) { zone.label = name; zone.name = name; + zone.type = getElemFieldVal(element, FIELD_TYPE); + zone.parent = getElemFieldVal(element, FIELD_PARENT); return; } @@ -649,6 +653,8 @@ export function updateElementInScenario(scenario, element) { nl.label = name; nl.name = name; + nl.type = getElemFieldVal(element, FIELD_TYPE); + nl.parent = getElemFieldVal(element, FIELD_PARENT); return; } @@ -698,6 +704,8 @@ export function updateElementInScenario(scenario, element) { pl.label = name; pl.name = name; + pl.type = getElemFieldVal(element, FIELD_TYPE); + pl.parent = getElemFieldVal(element, FIELD_PARENT); return; } diff --git a/playbooks/.ansible-lint b/playbooks/.ansible-lint new file mode 100644 index 0000000000000000000000000000000000000000..379b5568608d7714df4fe42f20f063180de6838e --- /dev/null +++ b/playbooks/.ansible-lint @@ -0,0 +1,6 @@ +skip_list: # rules to skip + - fqcn + - name + - risky-shell-pipe + - role-name[path] + - var-naming[no-role-prefix] \ No newline at end of file diff --git a/playbooks/README.md b/playbooks/README.md index 98442e5a4a52ba45106a583f47ac80b227d43363..cc814455050a4e2146d9090973b61d0142711d7a 100644 --- a/playbooks/README.md +++ b/playbooks/README.md @@ -1,3 +1,118 @@ -# AdvantEDGE Playbooks +# ETSI MEC Sandbox Ansible Setup + +This folder provides an **Ansible-based automation framework** to set up a multi-node Kubernetes cluster and deploy the ETSI MEC Sandbox platform. + +--- + +## Pre-requisites + +Before running the playbooks, ensure: + +1. **Ubuntu OS** (required by the setup script) +2. **Python 3** with `python3-venv` and `python3-pip` packages +3. Both repositories cloned as siblings: + - `etsi-mec-sandbox` (backend) + - `etsi-mec-sandbox-frontend` (frontend) +4. A **GitHub OAuth application** configured (Client ID & Secret) + +> **Note:** SSH setup is only required for remote worker nodes, not for localhost deployments. + +--- + +## Environment Setup (Required) + +Before running any playbooks, set up the Ansible environment: + +```bash +chmod +x ~/etsi-mec-sandbox/playbooks/setup_ansible_env.sh +cd ~/etsi-mec-sandbox/playbooks +./setup_ansible_env.sh +source ~/etsi-mec-sandbox/playbooks/ansible-venv/bin/activate +``` + +--- + +## Quick Start + +```bash +# Activate virtual environment +source ~/etsi-mec-sandbox/playbooks/ansible-venv/bin/activate + +# Run the playbook +cd ~/etsi-mec-sandbox/playbooks +ansible-playbook -i inventories/dev/hosts.ini site.yml +``` + +You will be prompted for: +- Sudo password +- MEC host IP/domain +- GitHub OAuth Client ID & Secret + +> **For detailed deployment instructions**, see [RUNBOOK.md](RUNBOOK.md) + +--- + +## Folder Structure + +``` +playbooks/ +├── setup_ansible_env.sh # Environment setup script (run first!) +├── site.yml # Main playbook entrypoint +├── ansible.cfg # Ansible configuration +├── collections/requirements.yml +├── inventories/dev/ +│ ├── hosts.ini # Inventory (hosts & groups) +│ └── group_vars/all.yml # Variables +└── roles/ # Ansible roles (see below) +``` + +--- + +## Roles Overview + +| Role | Purpose | +| ---------------------------- | ----------------------------------------- | +| **common** | Base system packages | +| **kernel** | Kernel modules & sysctl tuning | +| **containerd** | Containerd runtime | +| **docker** | Docker engine | +| **cni\_calico** | Calico CNI networking | +| **kubernetes/master** | Initialize Kubernetes control plane | +| **kubernetes/worker** | Join worker nodes to cluster | +| **helm** | Helm package manager | +| **dev\_env/golang** | Go development environment (conditional) | +| **dev\_env/node** | Node.js/NVM environment (conditional) | +| **mec\_sandbox/mec\_config** | Configure MEC Sandbox | +| **mec\_sandbox/mec\_deploy** | Build & deploy MEC Sandbox | + +--- + +## Key Variables + +Variables are defined in `inventories/dev/group_vars/all.yml`. + +| Variable | Default | Description | +|-----------------------|--------------|------------------------------------| +| `kubernetes_version` | `v1.35.1` | Kubernetes version | +| `calico_version` | `v3.31.4` | Calico CNI version | +| `install_dev_env` | `true` | Enable Go & Node.js setup | +| `install_mec_sandbox` | `true` | Enable MEC Sandbox deployment | + +--- + +## Documentation + +| Document | Description | +| ---------------------------- | ---------------------------------------------------- | +| **[RUNBOOK.md](RUNBOOK.md)** | Step-by-step deployment guide, troubleshooting, multi-node setup, and detailed configuration | + +--- + +## Notes + +* Run `setup_ansible_env.sh` first before executing any playbooks +* Both `etsi-mec-sandbox` and `etsi-mec-sandbox-frontend` repositories must be siblings +* Thanos/Prometheus failures during deployment are expected and ignored + +--- -This folder contains AdvantEDGE ansible playbooks. \ No newline at end of file diff --git a/playbooks/RUNBOOK.md b/playbooks/RUNBOOK.md new file mode 100644 index 0000000000000000000000000000000000000000..3e7b45b4e07c24e759ea76e9bd9fc4951f26f38c --- /dev/null +++ b/playbooks/RUNBOOK.md @@ -0,0 +1,310 @@ +# MEC Sandbox Ansible Deployment Guide + +This runbook provides step-by-step instructions for deploying the ETSI MEC Sandbox platform using Ansible. + +--- + +## Prerequisites + +Before running the playbooks, ensure you have: + +1. **Ubuntu OS** (required by the setup script) +2. **Python 3** with `python3-venv` and `python3-pip` packages +3. **Both repositories** cloned as siblings: + - `~/etsi-mec-sandbox` (backend) + - `~/etsi-mec-sandbox-frontend` (frontend) +4. **GitHub OAuth Application** credentials (Client ID & Client Secret) +5. A **target IP address or domain** for your MEC Sandbox installation + +--- + +## Environment Setup (Required First Step) + +Before running any playbooks, you must set up the Ansible environment: + +```bash +# Make the setup script executable +chmod +x ~/etsi-mec-sandbox/playbooks/setup_ansible_env.sh + +# Navigate to the playbooks directory +cd ~/etsi-mec-sandbox/playbooks + +# Run the setup script +./setup_ansible_env.sh + +# Activate the virtual environment +source ~/etsi-mec-sandbox/playbooks/ansible-venv/bin/activate +``` + +The setup script: +- Creates a Python virtual environment (`ansible-venv`) +- Installs `pip`, `ansible`, and `kubernetes` Python packages +- Installs Ansible collections from `collections/requirements.yml`: + - `community.general` + - `ansible.posix` + - `community.docker` + - `kubernetes.core` +- Updates `.gitignore` to exclude the virtual environment + +--- + +## Inventory Layout + +- **k8s_masters** → Control plane (API server, etcd, scheduler, controller-manager) +- **k8s_workers** → Optional worker nodes (run pods, kubelet, container runtime) + +Example `inventories/dev/hosts.ini`: +```ini +[k8s_masters] +localhost ansible_connection=local ansible_python_interpreter=auto_silent ansible_user= + +[k8s_workers] +# worker1 ansible_host=192.168.1.11 ansible_user=ubuntu +# worker2 ansible_host=192.168.1.12 ansible_user=ubuntu + +[all:vars] +ansible_become=true +ansible_become_method=sudo +``` + +--- + +## Quick Start (Single-Node Deployment) + +### Step 1: Setup Environment (if not done) + +```bash +chmod +x ~/etsi-mec-sandbox/playbooks/setup_ansible_env.sh +cd ~/etsi-mec-sandbox/playbooks +./setup_ansible_env.sh +source ~/etsi-mec-sandbox/playbooks/ansible-venv/bin/activate +``` + +### Step 2: Run the Playbook + +```bash +cd ~/etsi-mec-sandbox/playbooks +ansible-playbook -i inventories/dev/hosts.ini site.yml +``` + +You will be prompted for: +- **Sudo password**: Your local sudo password +- **MEC host address**: IP or domain (e.g., `192.168.1.100` or `mec.example.com`) +- **GitHub OAuth Client ID**: From your GitHub OAuth app +- **GitHub OAuth Client Secret**: From your GitHub OAuth app + +### Step 3: Verify Deployment + +After successful completion, access the MEC Sandbox at: +``` +https:// +``` + +--- + +## Execution Flow + +The playbook executes the following roles in order: + +| Order | Role | Description | +|-------|------------------------------|--------------------------------------------------| +| 1 | common | Base packages, APT keyring setup | +| 2 | kernel | Disable swap, kernel modules, sysctl tuning | +| 3 | containerd | Install & configure containerd with SystemdCgroup| +| 4 | docker | Docker engine installation & daemon config | +| 5 | kubernetes/master | Initialize Kubernetes control plane (kubeadm init)| +| 6 | cni_calico | Deploy Calico CNI via Tigera operator | +| 7 | helm | Install Helm package manager (via snap) | +| 8 | dev_env/golang (conditional) | Go development environment + GolangCI-Lint | +| 9 | dev_env/node (conditional) | Node.js/NVM environment | +| 10 | mec_sandbox/mec_config | Configure MEC Sandbox (charts, secrets, OAuth) | +| 11 | mec_sandbox/mec_deploy | Build and deploy MEC Sandbox components | + +--- + +## MEC Sandbox Deployment Details + +### mec_sandbox/mec_config Role + +This role configures the MEC Sandbox environment: + +1. **Adds kubectl bash completion** to `.bashrc` +2. **Updates /etc/hosts** with docker registry entry (`meep-docker-registry`) +3. **Copies Kubernetes CA** to system trust store (`/usr/local/share/ca-certificates/`) +4. **Runs `update-ca-certificates`** to refresh system CA store +5. **Restarts docker and containerd** daemons +6. **Patches chart values** (uid/gid 1001 → 1000): + - `charts/postgis/values.yaml` + - `charts/redis/values.yaml` + - `charts/docker-registry/values.yaml` +7. **Patches frontend config** (uid/gid 1001 → 1000): + - `etsi-mec-sandbox-frontend/config/.meepctl-repocfg.yaml` +8. **Updates GitHub OAuth credentials** in `secrets.yaml` +9. **Updates ingress host address** in `.meepctl-repocfg.yaml` + +### mec_sandbox/mec_deploy Role + +This role builds and deploys all MEC Sandbox components: + +1. **Install meepctl**: Runs `install.sh` from `go-apps/meepctl` +2. **Verify meepctl**: Checks `meepctl version` is available +3. **Configure meepctl**: + ```bash + meepctl config ip + meepctl config gitdir + ``` +4. **Build & Deploy Frontend**: + ```bash + cd etsi-mec-sandbox-frontend && bash build.sh && bash deploy.sh + ``` +5. **Configure Secrets**: Runs `configure-secrets.py set` +6. **Deploy Dependencies**: `meepctl deploy dep` (with up to 3 retries using `-f` flag) +7. **Build All**: `meepctl build --nolint all` +8. **Dockerize All**: `meepctl dockerize all` (runs via `sg docker`) +9. **Prune Docker Images**: `docker image prune -f` +10. **Deploy Core**: `meepctl deploy core` + +--- + +## Multi-node (Masters + Optional Workers) + +If you want to add worker nodes (separate machines), follow these steps: + +1. On each worker node, ensure SSH access is configured and Ansible can reach them. + +2. Edit `inventories/dev/hosts.ini` and add entries under `[k8s_workers]`: + ```ini + [k8s_workers] + worker1 ansible_host=192.168.56.11 ansible_user=ubuntu + worker2 ansible_host=192.168.56.12 ansible_user=ubuntu + ``` + +3. Uncomment the worker play in `site.yml`: + ```yaml + - hosts: k8s_workers + become: true + vars_prompt: + - name: ansible_become_pass + prompt: "Enter sudo password for workers" + private: true + roles: + - common + - kernel + - containerd + - kubernetes/common + - kubernetes/worker + ``` + +4. Run the playbook for master first (to initialize control plane and produce join script): + ```bash + ansible-playbook -l k8s_masters site.yml + ``` + After successful run, a join command will be generated at `/tmp/kubeadm_join.sh`. + +5. Copy the `/tmp/kubeadm_join.sh` to each worker node: + ```bash + scp /tmp/kubeadm_join.sh user@worker1:/tmp/kubeadm_join.sh + ``` + +6. Run the worker play: + ```bash + ansible-playbook -l k8s_workers site.yml + ``` + +--- + +## Conditional Roles + +The following roles can be enabled/disabled via variables in `group_vars/all.yml`: + +| Variable | Default | Description | +|----------------------|---------|--------------------------------------| +| `install_dev_env` | `true` | Install Go and Node.js environments | +| `install_mec_sandbox`| `true` | Configure and deploy MEC Sandbox | + +To skip MEC Sandbox deployment: +```bash +ansible-playbook -i inventories/dev/hosts.ini site.yml -e "install_mec_sandbox=false" +``` + +To skip development environment setup: +```bash +ansible-playbook -i inventories/dev/hosts.ini site.yml -e "install_dev_env=false" +``` + +--- + +## Troubleshooting + +### Virtual Environment Not Activated +If you see "ansible: command not found", activate the virtual environment: +```bash +source ~/etsi-mec-sandbox/playbooks/ansible-venv/bin/activate +``` + +### Thanos/Prometheus Deployment Failures +During `meepctl deploy dep`, thanos and prometheus failures are **expected and ignored**. The deployment will continue. + +### Repository Not Found Errors +Ensure both repositories are cloned as siblings: +``` +~/etsi-mec-sandbox/ +~/etsi-mec-sandbox-frontend/ +``` + +### Permission Issues (uid/gid 1001) +The `mec_config` role automatically patches chart values from uid/gid 1001 → 1000. If you still encounter issues, verify the patches were applied: +```bash +grep -r "runAsUser\|fsGroup" ~/etsi-mec-sandbox/charts/*/values.yaml +``` + +### Docker Group Issues +If dockerize fails, ensure your user is in the docker group: +```bash +sudo usermod -aG docker $USER +newgrp docker +``` + +### Kubernetes Collection Errors +If you see errors about `kubernetes.core.k8s`, ensure collections are installed: +```bash +source ~/etsi-mec-sandbox/playbooks/ansible-venv/bin/activate +ansible-galaxy collection install -r collections/requirements.yml +``` + +--- + +## Logs + +Deployment logs are saved to `/tmp/`: +- `/tmp/meepctl_deploy_dep.log` (and retry logs) +- `/tmp/meepctl_build.log` +- `/tmp/meepctl_dockerize.log` +- `/tmp/meepctl_deploy_core.log` + +--- + +## Key Variables + +Default values from `inventories/dev/group_vars/all.yml`: + +| Variable | Default Value | +|-----------------------|----------------------| +| `kubernetes_version` | `v1.35.1` | +| `calico_version` | `v3.31.4` | +| `containerd_version` | `1.7.27-1` | +| `pod_network_cidr` | `192.168.0.0/16` | +| `go_version` | `1.17` | +| `node_version` | `12.19.0` | +| `npm_version` | `6.14.8` | +| `eslint_version` | `5.16.0` | + +--- + +## Notes + +- **Always run `setup_ansible_env.sh` first** and activate the virtual environment +- Worker nodes will only run `common`, `kernel`, `containerd`, `kubernetes/common`, and `kubernetes/worker` roles +- The `kubernetes/worker` role expects a join script (created on master) at `/tmp/kubeadm_join.sh` +- The MEC Sandbox deployment requires significant resources; ensure adequate CPU, memory, and disk space +- The playbook uses `vars_prompt` for interactive input; for automation, pass variables via `-e` flag \ No newline at end of file diff --git a/playbooks/ansible.cfg b/playbooks/ansible.cfg index aba0c7884605f5b379d1e41f6c2575463758e6f1..a525a17b8d7f1495aa44b1807e01a02fcf3ee4bf 100644 --- a/playbooks/ansible.cfg +++ b/playbooks/ansible.cfg @@ -1,14 +1,11 @@ [defaults] -roles_path = ./roles -inventory = ./hosts.ini -remote_tmp = $HOME/.ansible/tmp -local_tmp = $HOME/.ansible/tmp -pipelining = True +inventory = inventories/dev/hosts.ini +roles_path = roles host_key_checking = False -deprecation_warnings = False -callback_whitelist = profile_tasks -ask_pass = True +stdout_callback = default +result_format = yaml +bin_ansible_callbacks = True +interpreter_python = auto -[privilege_escalation] -become = True -become_ask_pass = True +[ssh_connection] +pipelining = True \ No newline at end of file diff --git a/playbooks/collections/requirements.yml b/playbooks/collections/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..df33b450811eeb3a2e883ff953b987abeb839787 --- /dev/null +++ b/playbooks/collections/requirements.yml @@ -0,0 +1,7 @@ +collections: + - name: community.general + - name: ansible.posix + - name: community.docker + - name: ansible.posix + - name: kubernetes.core +roles: [] diff --git a/playbooks/group_vars/all.yml b/playbooks/group_vars/all.yml deleted file mode 100644 index 6a12c326d0f2a3f110ea656909528e78af5e893f..0000000000000000000000000000000000000000 --- a/playbooks/group_vars/all.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- - -# Ansible -# ansible_user: root -ansible_python_interpreter: /usr/bin/python3 - -# Ubuntu -ubuntu_dist: "Ubuntu" -ubuntu_release: "bionic" -ubuntu_dist_major: "18" -ubuntu_dist_version: "18.04" - -# Docker -docker_version: "5:20.10" - -# Containerd -containerd_version: "1.5.11-1" - -# Kubernetes -kube_version: "1.24" -cni_version: "0.8.7" -master_ip: "{{ hostvars[groups['master'][0]]['ansible_default_ipv4'].address | default(groups['master'][0]) }}" -network_dir: /etc/kubernetes/network -kubeadmin_config: /etc/kubernetes/admin.conf - -# Helm -helm_version: "3.7/stable" - -# Go -go_version: "1.18.1" -golangci_lint_version: "v1.46.0" - -# Node and NPM -node_version: "12.19.0" -npm_version: "6.14.8" -eslint_version: "5.16.0" \ No newline at end of file diff --git a/playbooks/hosts.ini b/playbooks/hosts.ini deleted file mode 100644 index 5990eb803a023ea6b5773412c8df3f0432f5bcee..0000000000000000000000000000000000000000 --- a/playbooks/hosts.ini +++ /dev/null @@ -1,17 +0,0 @@ -# AdvantEDGE cluster master node -[master] -#master1 ansible_host= ansible_user= - -# AdvantEDGE cluster worker nodes -[worker] -#worker1 ansible_host= ansible_user= -#worker2 ansible_host= ansible_user= - -# All cluster nodes (master + worker) -[cluster:children] -master -worker - -# AdvantEDGE development machine -[dev] -#dev1 ansible_host= ansible_user= \ No newline at end of file diff --git a/playbooks/install-development-env.yml b/playbooks/install-development-env.yml deleted file mode 100644 index 209d15611bb06cac81a7cefb164a3fe50e06251b..0000000000000000000000000000000000000000 --- a/playbooks/install-development-env.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- - -- hosts: dev - gather_facts: yes - become: yes - roles: - - { role: golang, tags: golang } - -- hosts: dev - gather_facts: yes - become: yes - roles: - - { role: node, tags: node } diff --git a/playbooks/install-runtime-env.yml b/playbooks/install-runtime-env.yml deleted file mode 100644 index a3853901a00d5f1375662bae14fab0a0e55a5524..0000000000000000000000000000000000000000 --- a/playbooks/install-runtime-env.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- - -- hosts: cluster - gather_facts: yes - become: yes - roles: - - { role: docker, tags: docker } - -- hosts: master - gather_facts: yes - become: yes - roles: - - { role: kubernetes/master, tags: master } - -- hosts: worker - gather_facts: yes - become: yes - roles: - - { role: kubernetes/worker, tags: worker } - -- hosts: master - gather_facts: yes - become: yes - roles: - - { role: helm, tags: helm } diff --git a/playbooks/inventories/dev/group_vars/all.yml b/playbooks/inventories/dev/group_vars/all.yml new file mode 100644 index 0000000000000000000000000000000000000000..994a9506e8deee74f48773b08672cea982897f6d --- /dev/null +++ b/playbooks/inventories/dev/group_vars/all.yml @@ -0,0 +1,85 @@ +# Global defaults +target_user: "{{ ansible_env.SUDO_USER | default(ansible_user_id) }}" +target_home: "{% if target_user == 'root' %}/root{% else %}/home/{{ target_user }}{% endif %}" + +apt_base_packages: + - ca-certificates + - curl + - gnupg + - lsb-release + - software-properties-common + - git + - unzip + - tar + - python3 + - python3-pip + - acl + +disable_swap: true + +# Container runtime +docker_package_state: present +containerd_version: "1.7.27-1" +containerd_config_path: /etc/containerd/config.toml + +# Docker (latest from official repo, no version pin) +docker_gpg_key_url: "https://download.docker.com/linux/ubuntu/gpg" +docker_gpg_key_path: "/usr/share/keyrings/docker-archive-keyring.gpg" +docker_repo_list_path: "/etc/apt/sources.list.d/docker.list" +docker_repo_url: "https://download.docker.com/linux/ubuntu" +docker_repo_component: "stable" +# System facts for repo (calculated dynamically in tasks, but you can override if needed) +docker_repo_arch: >- + {{ + 'amd64' if ansible_facts['architecture'] == 'x86_64' + else 'arm64' if ansible_facts['architecture'] == 'aarch64' + else ansible_facts['architecture'] + }} +docker_repo_codename: "{{ ansible_facts['lsb']['codename'] | default('jammy') }}" +docker_version: "5:29.2.1-1~ubuntu.{{ ansible_distribution_version }}~{{ docker_repo_codename }}" # Docker Version + + +# Kubernetes +kubernetes_version: "v1.35.1" # exact version for package installation/pinning +kubernetes_version_series: "v1.35" # minor version for repo URL + +kubernetes_repo_apt_key_url: >- + https://pkgs.k8s.io/core:/stable:/{{ kubernetes_version_series }}/deb/Release.key +kubernetes_repo_apt_entry: >- + deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] + https://pkgs.k8s.io/core:/stable:/{{ kubernetes_version_series }}/deb/ / +kubeadm_cluster_name: "mec-sandbox" +pod_network_cidr: "192.168.0.0/16" +service_cidr: "10.96.0.0/12" +apiserver_advertise_address: "127.0.0.1" + +# CNI (Calico) +calico_version: "v3.31.4" +calico_operator_crds_manifest: "https://raw.githubusercontent.com/projectcalico/calico/{{ calico_version }}/manifests/operator-crds.yaml" +calico_operator_manifest: "https://raw.githubusercontent.com/projectcalico/calico/{{ calico_version }}/manifests/tigera-operator.yaml" +calico_custom_resources_manifest: "https://raw.githubusercontent.com/projectcalico/calico/{{ calico_version }}/manifests/custom-resources-bpf.yaml" + +# Helm +helm_version: "v3.14.4" + +# Development environment (optional role) +install_dev_env: true +go_version: "1.17" +go_tar: "go{{ go_version }}.linux-amd64.tar.gz" +go_url: "https://go.dev/dl/go{{ go_version }}.linux-amd64.tar.gz" +node_major: 20 +node_version: "12.19.0" +npm_version: "6.14.8" +eslint_version: "5.16.0" +python_packages: [pyyaml] + +# MEC Sandbox paths (derived from target_home) +install_mec_sandbox: true +mec_sandbox_dir: "{{ target_home }}/etsi-mec-sandbox" +mec_frontend_dir: "{{ target_home }}/etsi-mec-sandbox-frontend" + +# Optional local registry & CA trust +docker_registry_host: "meep-docker-registry" # e.g., "registry.local:5000" +docker_insecure_registries: [] # e.g., ["registry.local:5000"] +docker_registry_mirrors: [] # e.g., ["https://mirror.gcr.io"] +trust_k8s_ca_for_runtime: true # if true, copy /etc/kubernetes/pki/ca.crt to runtime trust store diff --git a/playbooks/inventories/dev/hosts.ini b/playbooks/inventories/dev/hosts.ini new file mode 100644 index 0000000000000000000000000000000000000000..d5712d11b3f68be8e4cc86461a7b767dc8a955c5 --- /dev/null +++ b/playbooks/inventories/dev/hosts.ini @@ -0,0 +1,11 @@ +[k8s_masters] +localhost ansible_connection=local ansible_python_interpreter=auto_silent ansible_user=xflow + +# Optional: define worker nodes here. Example for remote hosts: +# [k8s_workers] +# worker1 ansible_host=192.168.40.59 ansible_user=ubuntu #change ansible_user +# worker2 ansible_host=192.168.56.12 ansible_user=ubuntu + +[all:vars] +ansible_become=true +ansible_become_method=sudo \ No newline at end of file diff --git a/playbooks/roles/cni_calico/tasks/main.yml b/playbooks/roles/cni_calico/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..072fd4a3145655455e11c8ae42840a9d95d5427a --- /dev/null +++ b/playbooks/roles/cni_calico/tasks/main.yml @@ -0,0 +1,140 @@ +--- +# - name: Check if calico-system namespace exists +# command: kubectl get ns tigera-operator --kubeconfig /etc/kubernetes/admin.conf +# register: calico_ns +# failed_when: false +# changed_when: false + +# - name: Install Calico operator +# when: calico_ns.rc != 0 +# command: > +# kubectl apply -f {{ calico_operator_manifest }} +# --kubeconfig /etc/kubernetes/admin.conf +# register: calico_operator_result +# changed_when: "'created' in calico_operator_result.stdout" + +# - name: Wait before applying Calico custom resources (allow operator to initialize) +# pause: +# seconds: 30 +# when: calico_ns.rc != 0 + +# - name: Install Calico custom resources +# when: calico_ns.rc != 0 +# command: > +# kubectl apply -f {{ calico_custom_resources_manifest }} +# --kubeconfig /etc/kubernetes/admin.conf +# register: calico_cr_result +# changed_when: "'created' in calico_cr_result.stdout" +# - block: +# - name: Create temporary kubeconfig directory +# file: +# path: /home/ansible/.kube +# state: directory +# mode: '0700' +# owner: ansible +# group: ansible + +# - name: Copy admin.conf to temporary kubeconfig +# copy: +# src: /etc/kubernetes/admin.conf +# dest: /home/ansible/.kube/config +# owner: ansible +# group: ansible +# mode: '0600' +- name: Ensure .kube directory exists for user + file: + path: "/home/{{ target_user }}/.kube" + state: directory + owner: "{{ target_user }}" + group: "{{ target_user }}" + mode: '0700' + become: true + +- name: copy admin.conf for user + become: true + copy: + src: /etc/kubernetes/admin.conf + dest: /home/{{ target_user }}/.kube/config + owner: "{{ target_user }}" + mode: '0600' + remote_src: true + +- name: Apply Calico operator CRDs + kubernetes.core.k8s: + kubeconfig: /home/{{ target_user }}/.kube/config + state: present + src: "{{ calico_operator_crds_manifest }}" + become: true + register: operator_crds_result + ignore_errors: true + +- name: Apply Calico operator manifest + kubernetes.core.k8s: + kubeconfig: /home/{{ target_user }}/.kube/config + state: present + src: "{{ calico_operator_manifest }}" + become: true + register: operator_manifest_result + ignore_errors: true + +- name: Wait for tigera-operator Deployment to be Ready + kubernetes.core.k8s: + kubeconfig: /home/{{ target_user }}/.kube/config + state: present + kind: Deployment + name: tigera-operator + namespace: tigera-operator + wait: true + wait_condition: + type: Available + status: "True" + become: true + when: operator_manifest_result is not failed + +- name: Apply Calico custom resources manifest + kubernetes.core.k8s: + kubeconfig: /home/{{ target_user }}/.kube/config + state: present + src: "{{ calico_custom_resources_manifest }}" + become: true + register: calico_custom_resources_result + +- name: Display CNI installation notice + debug: + msg: | + CNI (Calico) is being installed — this involves downloading container images and may take seconds to several minutes. + You can check the status in another terminal by running: + kubectl get po -A + +- name: Wait for Calico Installation to be ready + retries: 60 + delay: 30 + until: > + calico_installation.resources[0].status.conditions is defined + and (calico_installation.resources[0].status.conditions + | selectattr('type', 'equalto', 'Degraded') + | map(attribute='status') + | list | first) == "False" + kubernetes.core.k8s_info: + kubeconfig: /home/{{ target_user }}/.kube/config + kind: Installation + api_version: operator.tigera.io/v1 + name: default + register: calico_installation + become: true + +# - name: Remove master/control-plane taints to allow scheduling on single-node +# command: kubectl taint nodes {{ target_user }} {{ item }}- +# loop: +# - node-role.kubernetes.io/master +# - node-role.kubernetes.io/control-plane +# failed_when: false +# changed_when: false + +- name: Remove control-plane taint + command: kubectl taint nodes --all {{ item }}- + loop: + - node-role.kubernetes.io/master + - node-role.kubernetes.io/control-plane + failed_when: false + changed_when: false diff --git a/playbooks/roles/common/tasks/main.yml b/playbooks/roles/common/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..2cdc7ea70230829689223609c2de18924e55ff2a --- /dev/null +++ b/playbooks/roles/common/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Update apt cache and install base packages + apt: + update_cache: true + name: "{{ apt_base_packages }}" + state: present + +- name: Stop unattended-upgrades temporarily (to avoid apt lock) + ansible.builtin.systemd: + name: unattended-upgrades + state: stopped + register: stop_ua_result + failed_when: + - stop_ua_result is failed + - "'not-found' not in stop_ua_result.msg" + +- name: Ensure /etc/apt/keyrings exists + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' diff --git a/playbooks/roles/containerd/handlers/main.yml b/playbooks/roles/containerd/handlers/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..73b6693383134faba2ba5a15ae13cb1e4bc86bb6 --- /dev/null +++ b/playbooks/roles/containerd/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart containerd + systemd: + name: containerd + state: restarted + enabled: true + become: true diff --git a/playbooks/roles/containerd/tasks/main.yml b/playbooks/roles/containerd/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..ba00b28283110194f547e57b6f1e2b16ce032ac7 --- /dev/null +++ b/playbooks/roles/containerd/tasks/main.yml @@ -0,0 +1,52 @@ +--- +- name: Ensure Docker repo exists for containerd + import_role: + name: docker + tasks_from: repo.yml + +- name: Install containerd + apt: + name: "containerd.io={{ containerd_version }}" + state: present + allow_downgrade: true + update_cache: true + cache_valid_time: 3600 # Only updates cache if older than 1 hour + become: true + retries: 2 # try up to 3 times + delay: 5 # wait 10 between retries + +- name: Generate default containerd config + shell: containerd config default > {{ containerd_config_path }} + args: + executable: /bin/bash + become: true + changed_when: true + +- name: Ensure SystemdCgroup is true + replace: + path: "{{ containerd_config_path }}" + regexp: 'SystemdCgroup = false' + replace: 'SystemdCgroup = true' + become: true + notify: Restart containerd + +- name: Replace containerd sandbox image + replace: + path: "{{ containerd_config_path }}" + regexp: 'sandbox_image = "registry.k8s.io/pause:3.8' + replace: 'sandbox_image = "registry.k8s.io/pause:3.10' + become: true + notify: Restart containerd + changed_when: true + +- name: Trigger containerd restart if not ready + meta: flush_handlers + notify: Restart containerd + +- name: Debug - Containerd setup completed + debug: + msg: | + ✅ Containerd setup completed successfully: + - Installed + - Config generated + - SystemdCgroup enabled diff --git a/playbooks/roles/dev_env/golang/tasks/install.yml b/playbooks/roles/dev_env/golang/tasks/install.yml new file mode 100644 index 0000000000000000000000000000000000000000..333ca60a9da23f758505237cf135f1be85ef741c --- /dev/null +++ b/playbooks/roles/dev_env/golang/tasks/install.yml @@ -0,0 +1,70 @@ +--- + +# Step 1: Download Go tarball if not already installed at correct version +- name: Check if Go binary exists + stat: + path: /usr/local/go/bin/go + register: go_binary + +- name: Check Go version + command: /usr/local/go/bin/go version + register: go_version_output + changed_when: false + when: go_binary.stat.exists + +- name: Download Go tarball + get_url: + url: "https://go.dev/dl/go{{ go_version }}.linux-amd64.tar.gz" + dest: "/tmp/go{{ go_version }}.linux-amd64.tar.gz" + mode: "0644" + when: not go_binary.stat.exists or go_version_output.stdout is not search("go{{ go_version }}") + +# Step 2: Extract to /usr/local +- name: Extract Go tarball to /usr/local + shell: "tar -C /usr/local -xzf /tmp/go{{ go_version }}.linux-amd64.tar.gz" + args: + executable: /bin/bash + when: not go_binary.stat.exists or go_version_output.stdout is not search("go{{ go_version }}") + +# Step 3: Create ~/gocode/bin directory +- name: Create GOPATH bin directory + file: + path: "{{ target_home }}/gocode/bin" + state: directory + owner: "{{ target_user }}" + mode: "0755" + +# Step 4: Add Go environment to .bashrc (idempotent via blockinfile) +- name: Setup Go environment in .bashrc + blockinfile: + path: "{{ target_home }}/.bashrc" + marker: "# {mark} ANSIBLE MANAGED - Go environment setup" + block: | + # Go environment setup + export GOPATH=$HOME/gocode + export PATH=$PATH:$GOPATH/bin:/usr/local/go/bin + become: true + become_user: "{{ target_user }}" + +# Step 5: Install GolangCI-Lint +- name: Install GolangCI-Lint + shell: | + /usr/local/go/bin/go env -w GOPATH={{ target_home }}/gocode + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b {{ target_home }}/gocode/bin v1.46.0 + args: + executable: /bin/bash + creates: "{{ target_home }}/gocode/bin/golangci-lint" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ ansible_env.PATH }}" + GOPATH: "{{ target_home }}/gocode" + become: true + become_user: "{{ target_user }}" + +- name: Verify Go installation + command: /usr/local/go/bin/go version + register: go_final_version + changed_when: false + +- name: Show Go version + debug: + msg: "Go environment ready: {{ go_final_version.stdout }}, GOPATH={{ target_home }}/gocode" diff --git a/playbooks/roles/dev_env/golang/tasks/main.yml b/playbooks/roles/dev_env/golang/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..c6b506ecc62bd33a5ab7ec298d9cc5dda9344675 --- /dev/null +++ b/playbooks/roles/dev_env/golang/tasks/main.yml @@ -0,0 +1,2 @@ +- name: Setup Golang + import_tasks: install.yml diff --git a/playbooks/roles/node/files/install_nvm.sh b/playbooks/roles/dev_env/node/files/install_nvm.sh old mode 100755 new mode 100644 similarity index 100% rename from playbooks/roles/node/files/install_nvm.sh rename to playbooks/roles/dev_env/node/files/install_nvm.sh diff --git a/playbooks/roles/dev_env/node/tasks/install.yml b/playbooks/roles/dev_env/node/tasks/install.yml new file mode 100644 index 0000000000000000000000000000000000000000..55ce60eaaa0820fce7dd0fa257eaee9c1372844a --- /dev/null +++ b/playbooks/roles/dev_env/node/tasks/install.yml @@ -0,0 +1,49 @@ +--- + +- name: Install required system packages + apt: + name: "{{ item }}" + state: present + update_cache: true + with_items: + - build-essential + - libssl-dev + +- name: Check if nvm is installed + stat: + path: "{{ target_home }}/.nvm/nvm.sh" + register: nvm_installed + +- name: Download nvm install script + get_url: + url: https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh + dest: "{{ target_home }}/install_nvm.sh" + mode: '0755' + when: not nvm_installed.stat.exists + +- name: Install nvm + become: true + become_user: "{{ target_user }}" + shell: "bash {{ target_home }}/install_nvm.sh" + args: + executable: /bin/bash + creates: "{{ target_home }}/.nvm/nvm.sh" + +- name: Install node + become: true + become_user: "{{ target_user }}" + shell: /bin/bash -c "source {{ target_home }}/.nvm/nvm.sh && nvm install {{ node_version }}" + args: + executable: /bin/bash + creates: "{{ target_home }}/.nvm/versions/node/v{{ node_version }}" + +- name: Install npm and eslint + become: false + shell: > + source {{ target_home }}/.nvm/nvm.sh && + npm install -g npm@{{ npm_version }} && + npm install -g eslint@{{ eslint_version }} && + npm install -g eslint-plugin-react + args: + executable: /bin/bash + creates: "/home/{{ target_user }}/.nvm/versions/node/v{{ node_version }}/lib/node_modules/eslint" diff --git a/playbooks/roles/dev_env/node/tasks/main.yml b/playbooks/roles/dev_env/node/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..2deef836b3458e38822823ab444ca5ce994588e2 --- /dev/null +++ b/playbooks/roles/dev_env/node/tasks/main.yml @@ -0,0 +1,2 @@ +- name: Setup Node + import_tasks: install.yml diff --git a/playbooks/roles/docker/handlers/main.yml b/playbooks/roles/docker/handlers/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..825e7a5f2be04ff0b9e087cd3018862f1662a552 --- /dev/null +++ b/playbooks/roles/docker/handlers/main.yml @@ -0,0 +1,13 @@ +--- +- name: Restart docker + systemd: + name: docker + state: restarted + enabled: true + +- name: Restart containerd + systemd: + name: containerd + state: restarted + enabled: true + become: true diff --git a/playbooks/roles/docker/tasks/install.yml b/playbooks/roles/docker/tasks/install.yml index dff022b32832917c0da02909262ae121c86894e5..deda4ddf36c0ceaff0e4c86ac8e7ae4e8c44896e 100644 --- a/playbooks/roles/docker/tasks/install.yml +++ b/playbooks/roles/docker/tasks/install.yml @@ -1,75 +1,59 @@ --- -- name: Install aptitude using apt +- name: Install Docker engine and components apt: - name: aptitude - state: latest - update_cache: yes - force_apt_get: yes - -- name: Install required system packages - apt: - name: "{{ item }}" + name: + - "docker-ce={{ docker_version }}" + - "docker-ce-cli={{ docker_version }}" + - "docker-compose-plugin" state: present - update_cache: yes - with_items: - - apt-transport-https - - ca-certificates - - curl - - gnupg - - lsb-release - - software-properties-common - - python3-pip - - virtualenv - - python3-setuptools + notify: Restart docker -- name: Add Docker APT GPG key - apt_key: - url: https://download.docker.com/linux/ubuntu/gpg - state: present - -- name: Add Docker APT repository - apt_repository: - repo: deb https://download.docker.com/linux/ubuntu {{ ubuntu_release }} stable - state: present - -- name: Install docker engine - apt: - update_cache: yes - name: "{{ item }}" - state: present - with_items: - - docker-ce={{ docker_version }}* - - docker-ce-cli={{ docker_version }}* - - containerd.io={{ containerd_version }} - - docker-compose-plugin - -- name: Hold docker version +- name: Hold Docker packages dpkg_selections: name: "{{ item }}" selection: hold - with_items: + loop: - docker-ce - docker-ce-cli - - containerd.io - docker-compose-plugin -- name: Verify docker group is present - group: - name: docker - state: present - -- name: Add user to docker group +- name: Add user to Docker group user: - name: '{{ ansible_user }}' - append: yes + name: "{{ target_user }}" groups: docker + append: true + +- name: Reset ssh connection to pick up new docker group + meta: reset_connection + +- name: Ensure docker socket is group-accessible + file: + path: /var/run/docker.sock + group: docker + mode: "0660" + become: true + +# - name: Verify docker access as {{ target_user }} +# shell: "sg docker -c 'docker info > /dev/null 2>&1'" +# become: true +# become_user: "{{ target_user }}" +# changed_when: false +# register: docker_access_check +# failed_when: false + +# - name: Fallback — activate docker group via newgrp for current session +# shell: "sg docker -c 'docker ps > /dev/null'" +# become: true +# become_user: "{{ target_user }}" +# when: docker_access_check.rc != 0 +# changed_when: false -- name: Allow {{ ansible_user }} to access containerd socket +- name: Allow {{ target_user }} to access containerd socket acl: path: /run/containerd/containerd.sock etype: user - entity: '{{ ansible_user }}' + entity: '{{ target_user }}' permissions: rw - name: Set dockerd config @@ -78,17 +62,18 @@ dest: /etc/docker/ owner: root group: root - mode: 0644 - -- name: dockerd service - file: - path: /etc/systemd/system/docker.service.d - state: directory + mode: "0644" -- name: Enable and check Docker service - systemd: - name: docker - daemon_reload: yes - state: started - enabled: yes - register: started_docker +- name: Debug - Docker & Containerd setup completed + debug: + msg: | + ✅ Docker & Containerd setup completed successfully: + - GPG key added + - repo configured + - engine & plugins installed + - packages held + - user {{ target_user }} added to Docker group + - containerd config generated + - SystemdCgroup enabled + - sandbox image set to pause:3.10 + - containerd and docker restarted diff --git a/playbooks/roles/docker/tasks/main.yml b/playbooks/roles/docker/tasks/main.yml index d65ebe15e54dbbd36d6dcab9d892237acbc2498d..3ec997259eb578bb425341d765012facf9c75710 100644 --- a/playbooks/roles/docker/tasks/main.yml +++ b/playbooks/roles/docker/tasks/main.yml @@ -1,33 +1,6 @@ ---- - -- name: Assert OS is Ubuntu 18.04 - assert: - that: - - ansible_distribution == "{{ ubuntu_dist }}" - - ansible_distribution_major_version == "{{ ubuntu_dist_major }}" - - ansible_distribution_version == "{{ ubuntu_dist_version }}" - quiet: yes - -- name: Gather package facts - package_facts: - manager: apt - -- name: Get currently installed Docker version - set_fact: - installed_docker_version: "{{ ansible_facts.packages['docker-ce'][0].version }}" - when: - - "'docker-ce' in ansible_facts.packages" - -- name: Assert currently installed Docker version is supported - assert: - that: - - installed_docker_version is match("{{ docker_version }}*") - quiet: yes - when: - - installed_docker_version is defined +# roles/docker/tasks/main.yml +- name: Setup Docker repository + import_tasks: repo.yml - name: Install Docker - include_tasks: - file: install.yml - when: - - installed_docker_version is undefined \ No newline at end of file + import_tasks: install.yml diff --git a/playbooks/roles/docker/tasks/repo.yml b/playbooks/roles/docker/tasks/repo.yml new file mode 100644 index 0000000000000000000000000000000000000000..1daa2b93d9e6ec96f13f6caf032a0704785e45a5 --- /dev/null +++ b/playbooks/roles/docker/tasks/repo.yml @@ -0,0 +1,46 @@ +--- +# - name: Add Docker GPG key +# shell: | +# set -o pipefail +# curl -fsSL {{ docker_gpg_key_url }} | gpg --dearmor --yes -o {{ docker_gpg_key_path }} +# args: +# executable: /bin/bash +# creates: "{{ docker_gpg_key_path }}" +# become: true + +# - name: Add Docker repository +# shell: | +# set -o pipefail +# echo "deb [arch={{ docker_repo_arch }} signed-by={{ docker_gpg_key_path }}] {{ docker_repo_url }} {{ docker_repo_codename }} {{ docker_repo_component }}" \ +# | tee {{ docker_repo_list_path }} > /dev/null +# args: +# executable: /bin/bash +# creates: "{{ docker_repo_list_path }}" +# become: true + +- name: Ensure apt keyrings directory exists + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: '0755' + become: true + +- name: Download Docker GPG key file + ansible.builtin.get_url: + url: "{{ docker_gpg_key_url }}" + dest: "/etc/apt/keyrings/docker.asc" + mode: '0644' + become: true + +- name: Add Docker repository + ansible.builtin.apt_repository: + repo: > + deb [arch={{ docker_repo_arch }} + signed-by=/etc/apt/keyrings/docker.asc] + {{ docker_repo_url }} + {{ docker_repo_codename }} + {{ docker_repo_component }} + filename: docker + state: present + update_cache: true + become: true \ No newline at end of file diff --git a/playbooks/roles/golang/files/install.sh b/playbooks/roles/golang/files/install.sh deleted file mode 100644 index bc42117f827145980ae55c3e92d41d65d4b1a877..0000000000000000000000000000000000000000 --- a/playbooks/roles/golang/files/install.sh +++ /dev/null @@ -1,408 +0,0 @@ -#!/bin/sh -set -e -# Code generated by godownloader. DO NOT EDIT. -# - -usage() { - this=$1 - cat </dev/null -} -echoerr() { - echo "$@" 1>&2 -} -log_prefix() { - echo "$0" -} -_logp=6 -log_set_priority() { - _logp="$1" -} -log_priority() { - if test -z "$1"; then - echo "$_logp" - return - fi - [ "$1" -le "$_logp" ] -} -log_tag() { - case $1 in - 0) echo "emerg" ;; - 1) echo "alert" ;; - 2) echo "crit" ;; - 3) echo "err" ;; - 4) echo "warning" ;; - 5) echo "notice" ;; - 6) echo "info" ;; - 7) echo "debug" ;; - *) echo "$1" ;; - esac -} -log_debug() { - log_priority 7 || return 0 - echoerr "$(log_prefix)" "$(log_tag 7)" "$@" -} -log_info() { - log_priority 6 || return 0 - echoerr "$(log_prefix)" "$(log_tag 6)" "$@" -} -log_err() { - log_priority 3 || return 0 - echoerr "$(log_prefix)" "$(log_tag 3)" "$@" -} -log_crit() { - log_priority 2 || return 0 - echoerr "$(log_prefix)" "$(log_tag 2)" "$@" -} -uname_os() { - os=$(uname -s | tr '[:upper:]' '[:lower:]') - case "$os" in - msys*) os="windows" ;; - mingw*) os="windows" ;; - cygwin*) os="windows" ;; - win*) os="windows" ;; - esac - echo "$os" -} -uname_arch() { - arch=$(uname -m) - case $arch in - x86_64) arch="amd64" ;; - x86) arch="386" ;; - i686) arch="386" ;; - i386) arch="386" ;; - aarch64) arch="arm64" ;; - armv5*) arch="armv5" ;; - armv6*) arch="armv6" ;; - armv7*) arch="armv7" ;; - esac - echo ${arch} -} -uname_os_check() { - os=$(uname_os) - case "$os" in - darwin) return 0 ;; - dragonfly) return 0 ;; - freebsd) return 0 ;; - linux) return 0 ;; - android) return 0 ;; - nacl) return 0 ;; - netbsd) return 0 ;; - openbsd) return 0 ;; - plan9) return 0 ;; - solaris) return 0 ;; - windows) return 0 ;; - esac - log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" - return 1 -} -uname_arch_check() { - arch=$(uname_arch) - case "$arch" in - 386) return 0 ;; - amd64) return 0 ;; - arm64) return 0 ;; - armv5) return 0 ;; - armv6) return 0 ;; - armv7) return 0 ;; - ppc64) return 0 ;; - ppc64le) return 0 ;; - mips) return 0 ;; - mipsle) return 0 ;; - mips64) return 0 ;; - mips64le) return 0 ;; - s390x) return 0 ;; - amd64p32) return 0 ;; - esac - log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" - return 1 -} -untar() { - tarball=$1 - case "${tarball}" in - *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; - *.tar) tar --no-same-owner -xf "${tarball}" ;; - *.zip) unzip "${tarball}" ;; - *) - log_err "untar unknown archive format for ${tarball}" - return 1 - ;; - esac -} -http_download_curl() { - local_file=$1 - source_url=$2 - header=$3 - if [ -z "$header" ]; then - code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") - else - code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") - fi - if [ "$code" != "200" ]; then - log_debug "http_download_curl received HTTP status $code" - return 1 - fi - return 0 -} -http_download_wget() { - local_file=$1 - source_url=$2 - header=$3 - if [ -z "$header" ]; then - wget -q -O "$local_file" "$source_url" - else - wget -q --header "$header" -O "$local_file" "$source_url" - fi -} -http_download() { - log_debug "http_download $2" - if is_command curl; then - http_download_curl "$@" - return - elif is_command wget; then - http_download_wget "$@" - return - fi - log_crit "http_download unable to find wget or curl" - return 1 -} -http_copy() { - tmp=$(mktemp) - http_download "${tmp}" "$1" "$2" || return 1 - body=$(cat "$tmp") - rm -f "${tmp}" - echo "$body" -} -github_release() { - owner_repo=$1 - version=$2 - test -z "$version" && version="latest" - giturl="https://github.com/${owner_repo}/releases/${version}" - json=$(http_copy "$giturl" "Accept:application/json") - test -z "$json" && return 1 - version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') - test -z "$version" && return 1 - echo "$version" -} -hash_sha256() { - TARGET=${1:-/dev/stdin} - if is_command gsha256sum; then - hash=$(gsha256sum "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command sha256sum; then - hash=$(sha256sum "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command shasum; then - hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command openssl; then - hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f a - else - log_crit "hash_sha256 unable to find command to compute sha-256 hash" - return 1 - fi -} -hash_sha256_verify() { - TARGET=$1 - checksums=$2 - if [ -z "$checksums" ]; then - log_err "hash_sha256_verify checksum file not specified in arg2" - return 1 - fi - BASENAME=${TARGET##*/} - want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) - if [ -z "$want" ]; then - log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" - return 1 - fi - got=$(hash_sha256 "$TARGET") - if [ "$want" != "$got" ]; then - log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" - return 1 - fi -} -cat /dev/null < 0 + command: swapoff -a + changed_when: false + + - name: Comment out any active swap entries in fstab + replace: + path: /etc/fstab + regexp: '^([^#].*\s+swap\s+.*)$' + replace: '# \1' + notify: Reload systemd daemon + +- name: Ensure kernel modules are present + community.general.modprobe: + name: "{{ item }}" + state: present + loop: + - overlay + - br_netfilter + +- name: Persist kernel modules + copy: + dest: /etc/modules-load.d/k8s.conf + content: | + overlay + br_netfilter + mode: '0644' + +- name: Configure sysctl for Kubernetes networking + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_set: true + state: present + reload: true + loop: + - { name: net.bridge.bridge-nf-call-iptables, value: '1' } + - { name: net.bridge.bridge-nf-call-ip6tables, value: '1' } + - { name: net.ipv4.ip_forward, value: '1' } + +- name: Reload systemd (if needed) + ansible.builtin.systemd: + daemon_reload: true diff --git a/playbooks/roles/kubernetes/common/tasks/install.yml b/playbooks/roles/kubernetes/common/tasks/install.yml index 28a03ce69037252347738eea382d509223062dff..baa170ddd6fc9981e8cf6c1548e943a07c9e13b2 100644 --- a/playbooks/roles/kubernetes/common/tasks/install.yml +++ b/playbooks/roles/kubernetes/common/tasks/install.yml @@ -1,75 +1,67 @@ ---- - -- name: Install aptitude using apt - apt: - name: aptitude - state: latest - update_cache: yes - force_apt_get: yes - -- name: Install required system packages - apt: - name: "{{ item }}" - state: present - update_cache: yes - with_items: - - apt-transport-https - - curl - -- name: Add Kubernetes APT GPG key - apt_key: - url: https://packages.cloud.google.com/apt/doc/apt-key.gpg - state: present - -- name: Add Kubernetes APT repository - apt_repository: - repo: deb http://apt.kubernetes.io/ kubernetes-xenial main - state: present - filename: 'kubernetes' - -- name: Install kubernetes packages - apt: - name: "{{ item.name }}={{ item.version }}*" - update_cache: yes - force: yes - state: present - with_items: - - { name: kubectl, version: "{{ kube_version }}" } - - { name: kubelet, version: "{{ kube_version }}" } - - { name: kubeadm, version: "{{ kube_version }}" } - - { name: kubernetes-cni, version: "{{ cni_version }}" } - -- name: Hold kubernetes versions - dpkg_selections: - name: "{{ item }}" - selection: hold - with_items: - - kubelet - - kubeadm - - kubectl - - kubernetes-cni - -- name: kubelet service - file: - path: /etc/systemd/system/kubelet.service.d - state: directory - -- name: Reload kubelet - systemd: - name: kubelet - daemon_reload: yes - state: started - enabled: yes - register: started_kubelet - -- name: Kubectl auto-complete - lineinfile: - path: "/home/{{ ansible_user }}/.bashrc" - line: source <(kubectl completion bash) - create: yes - -- name: Add docker-registry to known hosts - lineinfile: - path: /etc/hosts - regexp: '^.*meep-docker-registry' - line: "{{ master_ip }} meep-docker-registry" +--- + +- name: Check if containerd is installed + command: which containerd + register: containerd_check + ignore_errors: true + changed_when: false + +- name: Include container runtime dependencies (ensure installed) + import_role: + name: containerd + when: containerd_check.rc != 0 + +- block: + - name: Install Kubeadm dependencies + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gpg + state: present + + # - name: Add Kubernetes GPG key safely + # shell: | + # set -o pipefail + # curl -fsSL {{ kubernetes_repo_apt_key_url }} | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg + # args: + # creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg + # executable: /bin/bash + + - name: Import Kubernetes GPG key + ansible.builtin.apt_key: + url: "{{ kubernetes_repo_apt_key_url }}" + state: present + keyring: /etc/apt/keyrings/kubernetes-apt-keyring.gpg + become: true + + - name: Add Kubernetes apt repository + apt_repository: + repo: "{{ kubernetes_repo_apt_entry }}" + state: present + filename: kubernetes + +- name: Install kube packages (kubeadm, kubelet, kubectl) + apt: + update_cache: true + name: + - "kubelet={{ kubernetes_version | regex_replace('v','') }}-*" + - "kubeadm={{ kubernetes_version | regex_replace('v','') }}-*" + - "kubectl={{ kubernetes_version | regex_replace('v','') }}-*" + state: present + +- name: Hold kube packages at installed versions + dpkg_selections: + name: "{{ item }}" + selection: hold + loop: + - kubelet + - kubeadm + - kubectl + +- name: Ensure kubelet is enabled and started + systemd: + name: kubelet + enabled: true + state: started diff --git a/playbooks/roles/kubernetes/common/tasks/main.yml b/playbooks/roles/kubernetes/common/tasks/main.yml index 3e9d3bbfc6fbb5fbb7c5da5250fcf579133d8277..a3635a523a6f17d358f07538cdd1cb794203861c 100644 --- a/playbooks/roles/kubernetes/common/tasks/main.yml +++ b/playbooks/roles/kubernetes/common/tasks/main.yml @@ -1,39 +1,5 @@ --- -- name: Assert OS is Ubuntu 18.04 - assert: - that: - - ansible_distribution == "{{ ubuntu_dist }}" - - ansible_distribution_major_version == "{{ ubuntu_dist_major }}" - - ansible_distribution_version == "{{ ubuntu_dist_version }}" - quiet: yes - -- name: Gather package facts - package_facts: - manager: apt - -- name: Get currently installed kubeadm version - set_fact: - installed_k8s_version: "{{ ansible_facts.packages['kubeadm'][0].version }}" - when: - - "'kubeadm' in ansible_facts.packages" - -- name: Assert currently installed Kubernetes version is supported - assert: - that: - - installed_k8s_version is match("{{ kube_version }}*") - quiet: yes - when: - - installed_k8s_version is defined - -- name: Disable swap - include_tasks: - file: swap.yml - when: - - installed_k8s_version is undefined - -- name: Install Kubernetes +- name: Install Kubernetes Dependencies include_tasks: file: install.yml - when: - - installed_k8s_version is undefined diff --git a/playbooks/roles/kubernetes/common/tasks/swap.yml b/playbooks/roles/kubernetes/common/tasks/swap.yml deleted file mode 100644 index 7efd4f9a8ad7aa32bf5605918debc08a8e917bf8..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/common/tasks/swap.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- - -- name: Disable system swap - shell: "swapoff -a" - -- name: Remove current swaps from fstab - lineinfile: - dest: /etc/fstab - regexp: '(?i)^([^#][\S]+\s+(none|swap)\s+swap.*)' - line: '# \1' - backrefs: yes - state: present - -- name: Disable swappiness and pass bridged IPv4 traffic to iptable's chains - sysctl: - name: "{{ item.name }}" - value: "{{ item.value }}" - state: present - with_items: - - { name: 'vm.swappiness', value: '0' } - - { name: 'net.bridge.bridge-nf-call-iptables', value: '1' } diff --git a/playbooks/roles/kubernetes/master/files/weave.yml b/playbooks/roles/kubernetes/master/files/weave.yml deleted file mode 100644 index 0a982f4d4082a1abe8da75a6580d7df162f60107..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/master/files/weave.yml +++ /dev/null @@ -1,257 +0,0 @@ -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ServiceAccount - metadata: - name: weave-net - annotations: - cloud.weave.works/launcher-info: |- - { - "original-request": { - "url": "/k8s/v1.15/net?env.WEAVE_MTU=1500", - "date": "Mon Apr 06 2020 14:16:03 GMT+0000 (UTC)" - }, - "email-address": "support@weave.works" - } - labels: - name: weave-net - namespace: kube-system - - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - name: weave-net - annotations: - cloud.weave.works/launcher-info: |- - { - "original-request": { - "url": "/k8s/v1.15/net?env.WEAVE_MTU=1500", - "date": "Mon Apr 06 2020 14:16:03 GMT+0000 (UTC)" - }, - "email-address": "support@weave.works" - } - labels: - name: weave-net - rules: - - apiGroups: - - '' - resources: - - pods - - namespaces - - nodes - verbs: - - get - - list - - watch - - apiGroups: - - networking.k8s.io - resources: - - networkpolicies - verbs: - - get - - list - - watch - - apiGroups: - - '' - resources: - - nodes/status - verbs: - - patch - - update - - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRoleBinding - metadata: - name: weave-net - annotations: - cloud.weave.works/launcher-info: |- - { - "original-request": { - "url": "/k8s/v1.15/net?env.WEAVE_MTU=1500", - "date": "Mon Apr 06 2020 14:16:03 GMT+0000 (UTC)" - }, - "email-address": "support@weave.works" - } - labels: - name: weave-net - roleRef: - kind: ClusterRole - name: weave-net - apiGroup: rbac.authorization.k8s.io - subjects: - - kind: ServiceAccount - name: weave-net - namespace: kube-system - - apiVersion: rbac.authorization.k8s.io/v1 - kind: Role - metadata: - name: weave-net - annotations: - cloud.weave.works/launcher-info: |- - { - "original-request": { - "url": "/k8s/v1.15/net?env.WEAVE_MTU=1500", - "date": "Mon Apr 06 2020 14:16:03 GMT+0000 (UTC)" - }, - "email-address": "support@weave.works" - } - labels: - name: weave-net - namespace: kube-system - rules: - - apiGroups: - - '' - resourceNames: - - weave-net - resources: - - configmaps - verbs: - - get - - update - - apiGroups: - - '' - resources: - - configmaps - verbs: - - create - - apiVersion: rbac.authorization.k8s.io/v1 - kind: RoleBinding - metadata: - name: weave-net - annotations: - cloud.weave.works/launcher-info: |- - { - "original-request": { - "url": "/k8s/v1.15/net?env.WEAVE_MTU=1500", - "date": "Mon Apr 06 2020 14:16:03 GMT+0000 (UTC)" - }, - "email-address": "support@weave.works" - } - labels: - name: weave-net - namespace: kube-system - roleRef: - kind: Role - name: weave-net - apiGroup: rbac.authorization.k8s.io - subjects: - - kind: ServiceAccount - name: weave-net - namespace: kube-system - - apiVersion: apps/v1 - kind: DaemonSet - metadata: - name: weave-net - annotations: - cloud.weave.works/launcher-info: |- - { - "original-request": { - "url": "/k8s/v1.15/net?env.WEAVE_MTU=1500", - "date": "Mon Apr 06 2020 14:16:03 GMT+0000 (UTC)" - }, - "email-address": "support@weave.works" - } - labels: - name: weave-net - namespace: kube-system - spec: - minReadySeconds: 5 - selector: - matchLabels: - name: weave-net - template: - metadata: - labels: - name: weave-net - spec: - containers: - - name: weave - command: - - /home/weave/launch.sh - env: - - name: HOSTNAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: spec.nodeName - - name: WEAVE_MTU - value: '1500' - image: 'docker.io/weaveworks/weave-kube:2.6.2' - readinessProbe: - httpGet: - host: 127.0.0.1 - path: /status - port: 6784 - resources: - requests: - cpu: 10m - securityContext: - privileged: true - volumeMounts: - - name: weavedb - mountPath: /weavedb - - name: cni-bin - mountPath: /host/opt - - name: cni-bin2 - mountPath: /host/home - - name: cni-conf - mountPath: /host/etc - - name: dbus - mountPath: /host/var/lib/dbus - - name: lib-modules - mountPath: /lib/modules - - name: xtables-lock - mountPath: /run/xtables.lock - - name: weave-npc - env: - - name: HOSTNAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: spec.nodeName - image: 'docker.io/weaveworks/weave-npc:2.6.2' - resources: - requests: - cpu: 10m - securityContext: - privileged: true - volumeMounts: - - name: xtables-lock - mountPath: /run/xtables.lock - dnsPolicy: ClusterFirstWithHostNet - hostNetwork: true - hostPID: true - priorityClassName: system-node-critical - restartPolicy: Always - securityContext: - seLinuxOptions: {} - serviceAccountName: weave-net - tolerations: - - effect: NoSchedule - operator: Exists - - effect: NoExecute - operator: Exists - volumes: - - name: weavedb - hostPath: - path: /var/lib/weave - - name: cni-bin - hostPath: - path: /opt - - name: cni-bin2 - hostPath: - path: /home - - name: cni-conf - hostPath: - path: /etc - - name: dbus - hostPath: - path: /var/lib/dbus - - name: lib-modules - hostPath: - path: /lib/modules - - name: xtables-lock - hostPath: - path: /run/xtables.lock - type: FileOrCreate - updateStrategy: - type: RollingUpdate diff --git a/playbooks/roles/kubernetes/master/handlers/main.yml b/playbooks/roles/kubernetes/master/handlers/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..73b6693383134faba2ba5a15ae13cb1e4bc86bb6 --- /dev/null +++ b/playbooks/roles/kubernetes/master/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart containerd + systemd: + name: containerd + state: restarted + enabled: true + become: true diff --git a/playbooks/roles/kubernetes/master/meta/main.yml b/playbooks/roles/kubernetes/master/meta/main.yml index c417757ee3f4fb6b18afdee96c8add5b9de976c3..3705c772446ebea1c511ae31df03f19539d9478e 100644 --- a/playbooks/roles/kubernetes/master/meta/main.yml +++ b/playbooks/roles/kubernetes/master/meta/main.yml @@ -1,4 +1,4 @@ ---- - -dependencies: - - { role: kubernetes/common } \ No newline at end of file +--- + +dependencies: + - { role: kubernetes/common } diff --git a/playbooks/roles/kubernetes/master/tasks/cni.yml b/playbooks/roles/kubernetes/master/tasks/cni.yml deleted file mode 100644 index a24425bb12d76cef75c326bee10671829a641b45..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/master/tasks/cni.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- - -- name: Create Kubernetes addon directory - file: - path: "{{ network_dir }}" - state: directory - -- name: "Copy Weave YAML files" - copy: - src: "weave.yml" - dest: "{{ network_dir }}/" - owner: root - group: root - mode: 0644 - -- name: "Create Weave network daemonset" - command: kubectl apply --kubeconfig={{ kubeadmin_config }} -f {{ network_dir }}/ - delegate_to: "{{ groups['master'][0] }}" - run_once: true diff --git a/playbooks/roles/kubernetes/master/tasks/init.yml b/playbooks/roles/kubernetes/master/tasks/init.yml deleted file mode 100644 index 1a1b3a95ed5d8a76578fa03009740f2686299084..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/master/tasks/init.yml +++ /dev/null @@ -1,74 +0,0 @@ ---- - -- name: Create Containerd config directory - file: - path: "/etc/containerd" - state: directory - -- name: Get containerd configuration - command: containerd config default - register: containerd_config - -- name: Save containerd configuration - copy: - content: "{{ containerd_config.stdout }}" - dest: /etc/containerd/config.toml - -- name: Restart Containerd service - systemd: - name: containerd - daemon_reload: yes - state: restarted - enabled: yes - -- name: Reset Kubernetes component - shell: "kubeadm reset --force" - register: reset_cluster - -- name: Init Kubernetes cluster - when: reset_cluster is succeeded - shell: kubeadm init --apiserver-advertise-address {{ master_ip }} --cri-socket unix:///run/containerd/containerd.sock - register: init_cluster - -- name: Create Kubernetes config directory - file: - path: ".kube/" - state: directory - -- name: Copy admin.conf to Home directory - when: init_cluster is succeeded - copy: - src: "{{ kubeadmin_config }}" - dest: ".kube/config" - owner: "{{ ansible_user | default(ansible_user_id) }}" - group: "{{ ansible_user | default(ansible_user_id) }}" - mode: 0755 - remote_src: true - -- name: "Enable scheduling on master node" - command: kubectl taint --kubeconfig={{ kubeadmin_config }} nodes --all node-role.kubernetes.io/master- - -- name: "Enable scheduling on control plane node" - command: kubectl taint --kubeconfig={{ kubeadmin_config }} nodes --all node-role.kubernetes.io/control-plane- - -- name: Enable and check kubelet service - systemd: - name: kubelet - daemon_reload: yes - state: started - enabled: yes - -- name: Add K8s CA to list of trusted CAs - copy: - src: /etc/kubernetes/pki/ca.crt - dest: /usr/local/share/ca-certificates/kubernetes-ca.crt - -- name: Update certificate index - shell: /usr/sbin/update-ca-certificates - -- name: Restart Containerd service - systemd: - name: containerd - daemon_reload: yes - state: restarted - enabled: yes diff --git a/playbooks/roles/kubernetes/master/tasks/main.yml b/playbooks/roles/kubernetes/master/tasks/main.yml index b67f932aa189d3ff9f0fbdb3dd240f628fc5e975..bd01bdc3d2670881d973606b076c17492911dfc0 100644 --- a/playbooks/roles/kubernetes/master/tasks/main.yml +++ b/playbooks/roles/kubernetes/master/tasks/main.yml @@ -1,20 +1,88 @@ ---- - -- name: Check if kubeadm has already run - stat: - path: "/etc/kubernetes/pki/ca.key" - register: kubeadm_ca - -- name: Init cluster if needed - include_tasks: init.yml - when: not kubeadm_ca.stat.exists - run_once: yes - -- name: Install CNI - include_tasks: cni.yml - when: not kubeadm_ca.stat.exists - run_once: yes - -- name: Create token - include_tasks: token.yml - run_once: yes +--- +# Kubernetes master setup + +- name: Check if Kubernetes control plane is already initialized + stat: + path: /etc/kubernetes/admin.conf + register: kube_admin_conf + become: true + +- name: Initialize Kubernetes control plane if not already initialized + when: not kube_admin_conf.stat.exists + block: + - name: Wait for containerd to be ready + command: crictl --runtime-endpoint unix:///run/containerd/containerd.sock info + register: crictl_info + retries: 5 + delay: 5 + until: crictl_info.rc == 0 + become: true + changed_when: false + + - name: Initialize Kubernetes control plane + command: kubeadm init --pod-network-cidr={{ pod_network_cidr }} + args: + creates: /etc/kubernetes/admin.conf + register: kubernetes_kubeadm_init + become: true + +- name: Create .kube directory for {{ target_user }} + file: + path: "{{ target_home }}/.kube" + state: directory + owner: "{{ target_user }}" + group: "{{ target_user }}" + mode: '0700' + +- name: Copy admin.conf to user kubeconfig + copy: + src: /etc/kubernetes/admin.conf + dest: "{{ target_home }}/.kube/config" + remote_src: true + owner: "{{ target_user }}" + group: "{{ target_user }}" + mode: '0600' + become: true + +- name: Create root kubeconfig directory + file: + path: /root/.kube + state: directory + mode: '0700' + when: target_user != 'root' + +- name: Copy admin.conf to root kubeconfig + copy: + src: /etc/kubernetes/admin.conf + dest: /root/.kube/config + remote_src: true + mode: '0600' + when: target_user != 'root' + +# - name: Enable scheduling on control plane node +# command: kubectl taint --kubeconfig={{ target_home }}/.kube/config nodes --all node-role.kubernetes.io/control-plane- +# when: '"node-role.kubernetes.io/control-plane" in kubernetes_taints.stdout' +# changed_when: false + +- name: Get kubeadm join command + command: kubeadm token create --print-join-command + register: kubeadm_join_cmd + changed_when: false + +- name: Save join command to file on master + copy: + content: "{{ kubeadm_join_cmd.stdout }}" + dest: /tmp/kubeadm_join.sh + mode: '0755' + +- name: Fetch join command to control node + fetch: + src: /tmp/kubeadm_join.sh + dest: /tmp/kubeadm_join.sh + flat: true + +- name: Print kubeadm init info + debug: + msg: + - "kubeadm init finished. If this is first master, kubeconfig copied to /root/.kube/config" + - "Join command for workers saved" diff --git a/playbooks/roles/kubernetes/master/tasks/token.yml b/playbooks/roles/kubernetes/master/tasks/token.yml deleted file mode 100644 index d1f40e19dda5fc141288164e7d2333e699e3a69e..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/master/tasks/token.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- - -- name: Create new token - shell: kubeadm token create - register: kubeadm_token_output - -- name: Save token in kubeadm_token variable - set_fact: - kubeadm_token: "{{ kubeadm_token_output.stdout }}" - -- name: Get Kubernetes CA hash - shell: | - openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ - openssl rsa -pubin -outform der 2>/dev/null | \ - openssl dgst -sha256 -hex | sed 's/^.* //' - register: kubeadm_hash_output - -- name: Save Kubernetes CA hash in kubeadm_hash variable - set_fact: - kubeadm_hash: "{{ kubeadm_hash_output.stdout }}" - -# - debug: -# msg: -# - "kubeadm_token: {{ kubeadm_token }}" -# - "kubeadm_hash: {{ kubeadm_hash }}" \ No newline at end of file diff --git a/playbooks/roles/kubernetes/worker/handlers/main.yml b/playbooks/roles/kubernetes/worker/handlers/main.yml deleted file mode 100644 index 432f8b568e1174675b15835a312a95742f91725f..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/worker/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Recreate kube-dns - command: kubectl --kubeconfig={{ kubeadmin_config }} -n kube-system delete pods -l k8s-app=kube-dns - delegate_to: "{{ groups['master'][0] }}" - run_once: true - ignore_errors: true diff --git a/playbooks/roles/kubernetes/worker/meta/main.yml b/playbooks/roles/kubernetes/worker/meta/main.yml deleted file mode 100644 index c417757ee3f4fb6b18afdee96c8add5b9de976c3..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/worker/meta/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- - -dependencies: - - { role: kubernetes/common } \ No newline at end of file diff --git a/playbooks/roles/kubernetes/worker/tasks/join.yml b/playbooks/roles/kubernetes/worker/tasks/join.yml deleted file mode 100644 index 5fc42ddf6f03300d2dbfebd477b0e943525386b1..0000000000000000000000000000000000000000 --- a/playbooks/roles/kubernetes/worker/tasks/join.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- - -- name: Reset Kubernetes component - shell: "kubeadm reset --force" - register: reset_cluster - -- name: Join to Kubernetes cluster - when: reset_cluster is succeeded - shell: | - kubeadm join {{ master_ip }}:6443 \ - --token {{ hostvars[groups['master'][0]]['kubeadm_token'] }} \ - --discovery-token-ca-cert-hash sha256:{{ hostvars[groups['master'][0]]['kubeadm_hash'] }} - register: join_cluster - notify: - - Recreate kube-dns - -- name: Add K8s CA to list of trusted CAs - copy: - src: /etc/kubernetes/pki/ca.crt - dest: /usr/local/share/ca-certificates/kubernetes-ca.crt - -- name: Update certificate index - shell: /usr/sbin/update-ca-certificates - -- name: Restart Docker service - systemd: - name: docker - daemon_reload: yes - state: restarted - enabled: yes - register: started_docker \ No newline at end of file diff --git a/playbooks/roles/kubernetes/worker/tasks/main.yml b/playbooks/roles/kubernetes/worker/tasks/main.yml index 708c23cdc3f3281b2c5b5684d282e4a52665ea2e..ca0acc5ba91deb49a94e83dffcf53c2c9352eb60 100644 --- a/playbooks/roles/kubernetes/worker/tasks/main.yml +++ b/playbooks/roles/kubernetes/worker/tasks/main.yml @@ -1,17 +1,21 @@ --- +# Kubernetes worker node setup -- name: Check if kubelet.conf exists - stat: - path: "/etc/kubernetes/kubelet.conf" - register: kubelet_conf - -- name: Join to cluster if needed - include_tasks: join.yml - when: not kubelet_conf.stat.exists - -- name: Enable and check kubelet service +- name: Ensure kubelet is enabled and started systemd: name: kubelet - daemon_reload: yes + enabled: true state: started - enabled: yes + +- name: Copy join command script to worker + copy: + src: /tmp/kubeadm_join.sh + dest: /tmp/kubeadm_join.sh + mode: '0755' + +- name: Join worker to cluster + command: sh /tmp/kubeadm_join.sh + args: + creates: /etc/kubernetes/kubelet.conf + +# --node-name {{ inventory_hostname }} diff --git a/playbooks/roles/mec_sandbox/mec_config/tasks/main.yml b/playbooks/roles/mec_sandbox/mec_config/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..60c43ffd88a1ce5fa0b7ddfaf7a0c593bdd82618 --- /dev/null +++ b/playbooks/roles/mec_sandbox/mec_config/tasks/main.yml @@ -0,0 +1,157 @@ +--- +# ============================================================ +# MEC Sandbox Configuration +# - Patch chart security contexts +# - Update secrets with user-provided GitHub OAuth creds +# - Update .meepctl-repocfg.yaml with user-provided IP/address +# ============================================================ + +- name: Add kubectl bash completion to .bashrc + lineinfile: + path: "{{ target_home }}/.bashrc" + line: 'source <(kubectl completion bash)' + state: present + become: true + become_user: "{{ target_user }}" + +- name: Add docker registry entry to /etc/hosts + lineinfile: + path: /etc/hosts + line: "{{ mec_host_address }} meep-docker-registry" + state: present + +- name: Copy Kubernetes CA cert to system trust store + copy: + src: /etc/kubernetes/pki/ca.crt + dest: /usr/local/share/ca-certificates/kubernetes-ca.crt + remote_src: true + mode: "0644" + +- name: Update system CA certificates + command: update-ca-certificates + changed_when: true + +- name: Restart docker daemon + systemd: + name: docker + state: restarted + +- name: Restart containerd daemon + systemd: + name: containerd + state: restarted + +- name: Verify etsi-mec-sandbox directory exists + stat: + path: "{{ mec_sandbox_dir }}" + register: sandbox_dir_check + +- name: Verify etsi-mec-sandbox-frontend directory exists + stat: + path: "{{ mec_frontend_dir }}" + register: frontend_dir_check + +- name: Fail if sandbox directory is missing + fail: + msg: > + etsi-mec-sandbox directory not found at {{ mec_sandbox_dir }}. + The backend repo (etsi-mec-sandbox) and frontend repo (etsi-mec-sandbox-frontend) + must be siblings under the same parent directory (e.g. ~/etsi-mec-sandbox and ~/etsi-mec-sandbox-frontend). + when: not sandbox_dir_check.stat.exists + +- name: Fail if frontend directory is missing + fail: + msg: > + etsi-mec-sandbox-frontend directory not found at {{ mec_frontend_dir }}. + The backend repo (etsi-mec-sandbox) and frontend repo (etsi-mec-sandbox-frontend) + must be siblings under the same parent directory (e.g. ~/etsi-mec-sandbox and ~/etsi-mec-sandbox-frontend). + when: not frontend_dir_check.stat.exists + +# --- Patch chart values: runAsUser/fsGroup 1001 → 1000 --- + +- name: "Patch postgis chart — fsGroup 1001 → 1000" + replace: + path: "{{ mec_sandbox_dir }}/charts/postgis/values.yaml" + regexp: 'fsGroup:\s*1001' + replace: 'fsGroup: 1000' + +- name: "Patch postgis chart — runAsUser 1001 → 1000" + replace: + path: "{{ mec_sandbox_dir }}/charts/postgis/values.yaml" + regexp: 'runAsUser:\s*1001' + replace: 'runAsUser: 1000' + +- name: "Patch redis chart — fsGroup 1001 → 1000" + replace: + path: "{{ mec_sandbox_dir }}/charts/redis/values.yaml" + regexp: 'fsGroup:\s*1001' + replace: 'fsGroup: 1000' + +- name: "Patch redis chart — runAsUser 1001 → 1000" + replace: + path: "{{ mec_sandbox_dir }}/charts/redis/values.yaml" + regexp: 'runAsUser:\s*1001' + replace: 'runAsUser: 1000' + +- name: "Patch docker-registry chart — fsGroup 1001 → 1000" + replace: + path: "{{ mec_sandbox_dir }}/charts/docker-registry/values.yaml" + regexp: 'fsGroup:\s*1001' + replace: 'fsGroup: 1000' + +- name: "Patch docker-registry chart — runAsUser 1001 → 1000" + replace: + path: "{{ mec_sandbox_dir }}/charts/docker-registry/values.yaml" + regexp: 'runAsUser:\s*1001' + replace: 'runAsUser: 1000' + +# --- Patch .meepctl-repocfg.yaml permissions uid/gid --- + +- name: "Patch repocfg — uid 1001 → 1000" + replace: + path: "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml" + regexp: 'uid:\s*1001' + replace: 'uid: 1000' + +- name: "Patch repocfg — gid 1001 → 1000" + replace: + path: "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml" + regexp: 'gid:\s*1001' + replace: 'gid: 1000' + +# --- Update secrets.yaml with user-provided GitHub OAuth --- + +- name: "Update GitHub OAuth client-id in secrets.yaml" + replace: + path: "{{ mec_frontend_dir }}/config/secrets.yaml" + regexp: 'client-id:\s*"my-github-client-id"' + replace: 'client-id: "{{ github_client_id }}"' + +- name: "Update GitHub OAuth secret in secrets.yaml" + replace: + path: "{{ mec_frontend_dir }}/config/secrets.yaml" + regexp: 'secret:\s*"my-github-secret"' + replace: 'secret: "{{ github_client_secret }}"' + +# --- Update .meepctl-repocfg.yaml with user-provided host --- + +- name: "Update ingress host in .meepctl-repocfg.yaml" + replace: + path: "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml" + regexp: 'host:\s*mec-platform\.etsi\.org' + replace: "host: {{ mec_host_address }}" + +- name: "Update GitHub redirect-uri in .meepctl-repocfg.yaml" + replace: + path: "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml" + regexp: 'redirect-uri:\s*https://mec-platform\.etsi\.org/platform-ctrl/v1/authorize' + replace: "redirect-uri: https://{{ mec_host_address }}/platform-ctrl/v1/authorize" + +- name: "Config complete" + debug: + msg: | + MEC Sandbox configuration applied: + - Chart security contexts patched (1001 → 1000) + - GitHub OAuth credentials updated in secrets.yaml + - Host set to {{ mec_host_address }} in .meepctl-repocfg.yaml + - Redirect URIs updated for GitHub and GitLab OAuth diff --git a/playbooks/roles/mec_sandbox/mec_deploy/tasks/main.yml b/playbooks/roles/mec_sandbox/mec_deploy/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..47d751cd3e3eb08266355ff4a53f31592b21dc49 --- /dev/null +++ b/playbooks/roles/mec_sandbox/mec_deploy/tasks/main.yml @@ -0,0 +1,296 @@ +--- +# ============================================================ +# MEC Sandbox Deployment +# - Install meepctl +# - Build & deploy frontend +# - Configure & deploy backend +# ============================================================ + +# --- Install meepctl --- + +- name: Install meepctl + shell: | + cd {{ mec_sandbox_dir }}/go-apps/meepctl + bash install.sh + args: + executable: /bin/bash + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ target_home }}/.nvm/versions/node/v{{ node_version }}/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + GOPATH: "{{ target_home }}/gocode" + HOME: "{{ target_home }}" + register: meepctl_install + changed_when: "'install' in meepctl_install.stdout" + +- name: Show meepctl install output + debug: + msg: "{{ meepctl_install.stdout_lines | default([]) }}" + +- name: Verify meepctl is available + command: "{{ target_home }}/gocode/bin/meepctl version" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ target_home }}/.nvm/versions/node/v{{ node_version }}/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + register: meepctl_version + changed_when: false + failed_when: meepctl_version.rc != 0 + +- name: Show meepctl version + debug: + msg: "meepctl installed: {{ meepctl_version.stdout }}" + +# --- Configure meepctl --- + +- name: "meepctl config ip" + shell: "meepctl config ip {{ mec_host_address }}" + args: + executable: /bin/bash + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + register: config_ip + +- name: Show meepctl config ip output + debug: + msg: "{{ config_ip.stdout_lines | default([]) }}" + +- name: "meepctl config gitdir" + shell: "meepctl config gitdir {{ mec_sandbox_dir }}" + args: + executable: /bin/bash + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + register: config_gitdir + +- name: Show meepctl config gitdir output + debug: + msg: "{{ config_gitdir.stdout_lines | default([]) }}" + +# --- Build & Deploy Frontend --- + +- name: Build frontend + shell: | + cd {{ mec_frontend_dir }} + bash build.sh + args: + executable: /bin/bash + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ target_home }}/.nvm/versions/node/v{{ node_version }}/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + register: frontend_build + +- name: Show frontend build output + debug: + msg: "{{ frontend_build.stdout_lines | default([]) }}" + +- name: Deploy frontend + shell: | + cd {{ mec_frontend_dir }} + bash deploy.sh + args: + executable: /bin/bash + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ target_home }}/.nvm/versions/node/v{{ node_version }}/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + register: frontend_deploy + +- name: Show frontend deploy output + debug: + msg: "{{ frontend_deploy.stdout_lines | default([]) }}" + +# --- Configure secrets --- + +- name: Configure secrets via python script + shell: "python3 {{ mec_sandbox_dir }}/config/configure-secrets.py set {{ mec_sandbox_dir }}/config/secrets.yaml" + args: + executable: /bin/bash + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + register: config_secrets + +- name: Show configure secrets output + debug: + msg: "{{ config_secrets.stdout_lines | default([]) }}" + +# --- Deploy dependencies (with retries, ignoring thanos/prometheus failures) --- + +- name: "Deploy dependencies (meepctl deploy dep)" + shell: "meepctl deploy dep 2>&1 | tee /tmp/meepctl_deploy_dep.log" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: dep_deploy + ignore_errors: true + +- name: Show deploy dep output + debug: + msg: "{{ dep_deploy.stdout_lines | default([]) }}" + +- name: "Retry deploy dep with force if failed (attempt 1 of 3)" + shell: "meepctl deploy dep -f 2>&1 | tee /tmp/meepctl_deploy_dep_retry1.log" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: dep_deploy_retry1 + when: dep_deploy.rc != 0 + ignore_errors: true + +- name: "Retry deploy dep with force if failed (attempt 2 of 3)" + shell: "meepctl deploy dep -f 2>&1 | tee /tmp/meepctl_deploy_dep_retry2.log" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: dep_deploy_retry2 + when: dep_deploy_retry1 is defined and dep_deploy_retry1.rc is defined and dep_deploy_retry1.rc != 0 + ignore_errors: true + +- name: "Retry deploy dep with force if failed (attempt 3 of 3)" + shell: "meepctl deploy dep -f 2>&1 | tee /tmp/meepctl_deploy_dep_retry3.log" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: dep_deploy_retry3 + when: dep_deploy_retry2 is defined and dep_deploy_retry2.rc is defined and dep_deploy_retry2.rc != 0 + ignore_errors: true + +- name: Determine final deploy dep result + set_fact: + dep_final: >- + {{ + dep_deploy_retry3 if (dep_deploy_retry3 is defined and dep_deploy_retry3.rc is defined) else + (dep_deploy_retry2 if (dep_deploy_retry2 is defined and dep_deploy_retry2.rc is defined) else + (dep_deploy_retry1 if (dep_deploy_retry1 is defined and dep_deploy_retry1.rc is defined) else + dep_deploy)) + }} + +- name: Check for non-thanos/prometheus failures in deploy dep + fail: + msg: | + Dependency deployment failed after retries. + Output: {{ dep_final.stdout | default('') }} + Errors: {{ dep_final.stderr | default('') }} + Note: thanos and prometheus failures are expected and can be ignored. + when: > + dep_final.rc is defined and dep_final.rc != 0 and + 'thanos' not in (dep_final.stderr | default('') | lower) and + 'prometheus' not in (dep_final.stderr | default('') | lower) and + 'thanos' not in (dep_final.stdout | default('') | lower) and + 'prometheus' not in (dep_final.stdout | default('') | lower) + +- name: "Note on deploy dep result" + debug: + msg: > + Dependency deployment completed. + If thanos/prometheus failed, that is expected and ignored. + +# --- Build all --- + +- name: "Build all (meepctl build --nolint all)" + shell: "meepctl build --nolint all 2>&1 | tee /tmp/meepctl_build.log" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ target_home }}/.nvm/versions/node/v{{ node_version }}/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + GOPATH: "{{ target_home }}/gocode" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: build_all + +- name: Show build all output + debug: + msg: "{{ build_all.stdout_lines | default([]) }}" + +# --- Dockerize all --- + +- name: "Dockerize all (meepctl dockerize all)" + shell: "sg docker -c 'meepctl dockerize all 2>&1 | tee /tmp/meepctl_dockerize.log'" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:{{ target_home }}/.nvm/versions/node/v{{ node_version }}/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + GOPATH: "{{ target_home }}/gocode" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: dockerize_all + +- name: Show dockerize all output + debug: + msg: "{{ dockerize_all.stdout_lines | default([]) }}" + +# --- Prune docker images --- + +- name: "Prune dangling Docker images" + shell: "docker image prune -f" + args: + executable: /bin/bash + become: true + +# --- Deploy core --- + +- name: "Deploy core (meepctl deploy core)" + shell: "meepctl deploy core 2>&1 | tee /tmp/meepctl_deploy_core.log" + args: + executable: /bin/bash + chdir: "{{ mec_sandbox_dir }}" + become: true + become_user: "{{ target_user }}" + environment: + PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin" + HOME: "{{ target_home }}" + KUBECONFIG: "{{ target_home }}/.kube/config" + register: deploy_core + +- name: Show deploy core output + debug: + msg: "{{ deploy_core.stdout_lines | default([]) }}" + +- name: "MEC Sandbox deployment complete" + debug: + msg: | + MEC Sandbox fully deployed! + Access at: https://{{ mec_host_address }} diff --git a/playbooks/roles/node/tasks/install.yml b/playbooks/roles/node/tasks/install.yml deleted file mode 100644 index 8cf464321781d75d6bf41df825cc593d90e0a020..0000000000000000000000000000000000000000 --- a/playbooks/roles/node/tasks/install.yml +++ /dev/null @@ -1,32 +0,0 @@ ---- - -- name: Install required system packages - apt: - name: "{{ item }}" - state: present - update_cache: yes - with_items: - - build-essential - - libssl-dev - -- name: "Copy nvm file" - copy: - src: "install_nvm.sh" - dest: "install_nvm.sh" - mode: 0744 - tags: node - -- name: "Install nvm" - become: false - shell: "bash install_nvm.sh" - tags: node - args: - executable: /bin/bash - -- name: Install node - become: false - shell: /bin/bash -c "source /home/{{ ansible_user }}/.nvm/nvm.sh && nvm install {{ node_version }}" - -- name: Install npm and eslint - become: false - shell: /bin/bash -c "source /home/{{ ansible_user }}/.nvm/nvm.sh && npm install -g npm@{{ npm_version }} && npm install -g eslint@{{ eslint_version }} && npm install -g eslint-plugin-react" diff --git a/playbooks/roles/node/tasks/main.yml b/playbooks/roles/node/tasks/main.yml deleted file mode 100644 index 50898d308e8f70103537e091de3f0d680245fe88..0000000000000000000000000000000000000000 --- a/playbooks/roles/node/tasks/main.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -- name: Assert OS is Ubuntu 18.04 - assert: - that: - - ansible_distribution == "{{ ubuntu_dist }}" - - ansible_distribution_major_version == "{{ ubuntu_dist_major }}" - - ansible_distribution_version == "{{ ubuntu_dist_version }}" - quiet: yes - -- name: Check if node is installed - command: /bin/bash -c "source /home/{{ ansible_user }}/.nvm/nvm.sh; node -v | cut -c 2-" - register: node_check - failed_when: "'command not found' in node_check.stdout" - -- debug: - msg: "[WARNING] Current node version is lower than recommended version {{ node_version }}.\n - To get the script to install properly, uninstall the existing node and rerun script.\n - Also remove the .nvm folder" - when: node_check.stdout != '' and node_check.stdout is version_compare(node_version, '<') - -- name: Check if npm is installed - command: /bin/bash -c "source /home/{{ ansible_user }}/.nvm/nvm.sh; npm -v" - register: npm_check - failed_when: "'command not found' in npm_check.stdout" - -- debug: - msg: "[WARNING] Current npm version is lower than recommended version {{ npm_version }}.\n - To get the script to install properly, uninstall the existing npm and rerun script.\n - Also remove the .nvm folder" - when: npm_check.stdout != '' and npm_check.stdout is version_compare(npm_version, '<') - -- name: Install Node and NPM if node or npm command not found - include_tasks: - file: install.yml - when: node_check.stdout == '' and npm_check.stdout == '' diff --git a/playbooks/setup_ansible_env.sh b/playbooks/setup_ansible_env.sh new file mode 100755 index 0000000000000000000000000000000000000000..9af00e230ca908df98b7644369b67ffa98800678 --- /dev/null +++ b/playbooks/setup_ansible_env.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +set -euo pipefail + +PLAYBOOK_DIR="$HOME/etsi-mec-sandbox/playbooks" +SANDBOX_DIR="$HOME/etsi-mec-sandbox" +VENV_NAME="ansible-venv" +VENV_PATH="$PLAYBOOK_DIR/$VENV_NAME" +COLLECTION_REQ="$PLAYBOOK_DIR/collections/requirements.yml" +GITIGNORE_FILE="$SANDBOX_DIR/.gitignore" +GITIGNORE_ENTRY="playbooks/$VENV_NAME/" + +error() { echo "ERROR: $1" >&2; exit 1; } +command_exists() { command -v "$1" >/dev/null 2>&1; } + +require_ubuntu() { + [[ -f /etc/os-release ]] || error "Cannot detect OS." + . /etc/os-release + [[ "$ID" == "ubuntu" ]] || error "This script supports Ubuntu only." +} + +confirm_install() { + local pkg="$1" + read -r -p "$pkg is missing. Install it now? [y/N]: " ans + [[ "$ans" =~ ^[Yy]$ ]] || error "$pkg is required. Aborting." + sudo apt-get update + sudo apt-get install -y "$pkg" +} + +ensure_package() { + local cmd="$1" pkg="$2" + command_exists "$cmd" || confirm_install "$pkg" +} + +create_venv() { + if [[ -d "$VENV_PATH" ]]; then + echo "Virtual environment exists, skipping creation." + else + python3 -m venv "$VENV_PATH" + echo "Virtual environment created at $VENV_PATH" + fi +} + +activate_venv() { source "$VENV_PATH/bin/activate"; } + +install_python_packages() { + pip install --upgrade pip + pip install kubernetes ansible +} + +install_ansible_collections() { + [[ -f "$COLLECTION_REQ" ]] || error "Missing $COLLECTION_REQ" + ansible-galaxy collection install -r "$COLLECTION_REQ" || true +} + +update_gitignore() { + [[ -f "$GITIGNORE_FILE" ]] || touch "$GITIGNORE_FILE" + + if ! grep -q "^playbooks/$VENV_NAME" "$GITIGNORE_FILE"; then + echo "playbooks/$VENV_NAME/" >> "$GITIGNORE_FILE" + echo "Added playbooks/$VENV_NAME/ to .gitignore" + else + echo ".gitignore already contains entry for $VENV_NAME" + fi +} + +main() { + require_ubuntu + + ensure_package python3 python3 + ensure_package python3 -m venv python3-venv + ensure_package pip3 python3-pip + + create_venv + activate_venv + + install_python_packages + install_ansible_collections + update_gitignore + + echo "Setup complete. Activate environment with:" + echo "source $VENV_PATH/bin/activate" +} + +main \ No newline at end of file diff --git a/playbooks/site.yml b/playbooks/site.yml new file mode 100644 index 0000000000000000000000000000000000000000..fb25d753eec7acff27a6808231b3c1f481577ac6 --- /dev/null +++ b/playbooks/site.yml @@ -0,0 +1,46 @@ +--- + +- hosts: k8s_masters + become: true + vars_prompt: + - name: ansible_become_pass + prompt: "Enter sudo password for master" + private: true + - name: mec_host_address + prompt: "Enter the IP or domain for MEC Sandbox (e.g. 192.168.1.100 or mec.example.com)" + private: false + - name: github_client_id + prompt: "Enter GitHub OAuth Client ID" + private: false + - name: github_client_secret + prompt: "Enter GitHub OAuth Client Secret" + private: true + roles: + - common + - kernel + - containerd + - docker + - kubernetes/master + - cni_calico + - helm + - role: dev_env/golang + when: install_dev_env + - role: dev_env/node + when: install_dev_env + - role: mec_sandbox/mec_config + when: install_mec_sandbox + - role: mec_sandbox/mec_deploy + when: install_mec_sandbox + +# - hosts: k8s_workers +# become: true +# vars_prompt: +# - name: ansible_become_pass +# prompt: "Enter sudo password for workers" +# private: true +# roles: +# - common +# - kernel +# - containerd +# - kubernetes/common +# - kubernetes/worker diff --git a/playbooks/uninstall-runtime-env.yml b/playbooks/uninstall-runtime-env.yml deleted file mode 100644 index 85ea5144e9b7e8f0373048d2747736295065a45e..0000000000000000000000000000000000000000 --- a/playbooks/uninstall-runtime-env.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- - -- hosts: cluster - gather_facts: no - become: yes - tasks: - - name: Reset kubeadm - shell: "kubeadm reset --force" - ignore_errors: True - - - name: Unhold kubernetes versions - dpkg_selections: - name: "{{ item }}" - selection: purge - with_items: - - kubelet - - kubeadm - - kubectl - - kubernetes-cni - - - name: Uninstall kubernetes packages - apt: - name: "{{ item }}" - update_cache: yes - state: absent - purge: yes - with_items: - - kubectl - - kubelet - - kubeadm - - kubernetes-cni \ No newline at end of file diff --git a/scripts/build_all.sh b/scripts/build_all.sh index f41ff0a27c5f1e30a316f0210a6de2d6876a1f54..f8747be4f91074d347d2b6292a2d77b566c19b63 100755 --- a/scripts/build_all.sh +++ b/scripts/build_all.sh @@ -46,6 +46,10 @@ build_examples() { ./docker_build.sh && ./dockerize.sh cd $ADV_PATH/examples/demo6/python #./docker_build.sh && ./dockerize.sh + cd $ADV_PATH/examples/demo9/golang + ./docker_build.sh && ./dockerize.sh + cd $ADV_PATH/examples/demo10 + ./docker_build.sh && ./dockerize.sh } usage() {