From 340b611874d275486b0b510887fa24050ebe9fee Mon Sep 17 00:00:00 2001 From: yangalicace1 <royer.yangali@etsi.org> Date: Mon, 21 Oct 2024 13:41:03 +0000 Subject: [PATCH] Deployed 4be9976 to develop in public with MkDocs 1.6.1 and mike 2.1.3 --- .../deployment_guide/index.html | 242 +++++++++++++++++- .../deployment_guide/01_vagrant_box.jpg | Bin 0 -> 59846 bytes .../deployment_guide/02_vagrant_box.jpg | Bin 0 -> 21135 bytes .../deployment_guide/03_vagrant_box.jpg | Bin 0 -> 25920 bytes .../deployment_guide/04_vagrant_box.jpg | Bin 0 -> 28683 bytes .../deployment_guide/05_vagrant_box.jpg | Bin 0 -> 25785 bytes .../deployment_guide/06_vagrant_box.jpg | Bin 0 -> 25503 bytes .../deployment_guide/07_vagrant_box.jpg | Bin 0 -> 26303 bytes .../deployment_guide/08_vagrant_box.jpg | Bin 0 -> 25022 bytes public/develop/search/search_index.json | 2 +- 10 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 public/develop/images/deployment_guide/01_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/02_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/03_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/04_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/05_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/06_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/07_vagrant_box.jpg create mode 100644 public/develop/images/deployment_guide/08_vagrant_box.jpg diff --git a/public/develop/deployment_guide/deployment_guide/index.html b/public/develop/deployment_guide/deployment_guide/index.html index 51d0c9c..678db6f 100644 --- a/public/develop/deployment_guide/deployment_guide/index.html +++ b/public/develop/deployment_guide/deployment_guide/index.html @@ -833,7 +833,247 @@ sudo apt-get dist-upgrade -y <li>Restart the VM when the installation is completed.</li> </ul> <h3 id="115-vagrant-box"><strong>1.1.5. Vagrant Box</strong></h3> -<p><TBD_LONG></p> +<p>This section describes how to create a Vagrant Box, using the base virtual machine configured in <a href="#112-oracle-virtual-box">Oracle Virtual Box</a>.</p> +<h3><u>Virtual Machine specifications</h3> +<p></u> +Most of the specifications can be as specified in the <a href="#112-oracle-virtual-box">Oracle Virtual Box</a> page, however, there are a few particularities to Vagrant that must be accommodated, such as:</p> +<ul> +<li>Virtual Hard Disk</li> +<li>Size: 60GB (at least)</li> +<li><strong>Type</strong>: VMDK</li> +</ul> +<p><img alt="spaces_huDzAu5hmjUdNzCGBBbL_uploads_jrerlmLyZWi5f2Tzb7xY_Screenshot_from_2023-07-10_18-13-43" src="/documentation/doc/images/deployment_guide/01_vagrant_box.jpg" /></p> +<p>Also, before initiating the VM and installing the OS, we'll need to:</p> +<ul> +<li>Disable Floppy in the 'Boot Order'</li> +<li>Disable audio</li> +<li>Disable USB</li> +<li>Ensure Network Adapter 1 is set to NAT</li> +</ul> +<h3><u>Network configurations</h3> +<p></u> +At Network Adapt 1, the following port-forwarding rule must be set.</p> +<table> +<thead> +<tr> +<th>Name</th> +<th>Protocol</th> +<th>Host IP</th> +<th>Host Port</th> +<th>Guest IP</th> +<th>Guest Port</th> +</tr> +</thead> +<tbody> +<tr> +<td>SSH</td> +<td>TCP</td> +<td></td> +<td><strong>2222</strong></td> +<td></td> +<td>22</td> +</tr> +</tbody> +</table> +<p><img alt="Screenshot_from_2023-07-10_18-25-18" src="uploads/ced8e7b1133d6831e0c203801b6ba448/Screenshot_from_2023-07-10_18-25-18.png" /></p> +<h3><u>Installing the OS</h3> +<p></u></p> +<p>For a Vagrant Box, it is generally suggested that the ISO's server version is used, as it is intended to be used via SSH, and any web GUI is expected to be forwarded to the host.</p> +<p><img alt="Screenshot_from_2023-07-10_18-41-49" src="/documentation/doc/images/deployment_guide/02_vagrant_box.jpg" /></p> +<p><img alt="Screenshot_from_2023-07-10_18-42-30" src="/documentation/doc/images/deployment_guide/03_vagrant_box.jpg" /></p> +<p><img alt="Screenshot_from_2023-07-10_18-42-45" src="/documentation/doc/images/deployment_guide/04_vagrant_box.jpg" /></p> +<p>Make sure the disk is not configured as an LVM group!</p> +<p><img alt="Screenshot_from_2023-07-10_18-43-16" src="/documentation/doc/images/deployment_guide/05_vagrant_box.jpg" /></p> +<h3><u>Vagrant ser</h3> +<p></u> +Vagrant expects by default, that in the box's OS exists the user <code>vagrant</code> with the password also being <code>vagrant</code>.</p> +<p><img alt="Screenshot_from_2023-07-10_18-54-12" src="/documentation/doc/images/deployment_guide/06_vagrant_box.jpg" /></p> +<h3><u>SSH</h3> +<p></u></p> +<p>Vagrant uses SSH to connect to the boxes, so installing it now will save the hassle of doing it later.</p> +<p><img alt="Screenshot_from_2023-07-10_18-54-48" src="/documentation/doc/images/deployment_guide/07_vagrant_box.jpg" /></p> +<h3><u>Features server snaps</h3> +<p></u></p> +<p>Do not install featured server snaps. It will be done manually <a href="#12-install-microk8s">later</a> to illustrate how to uninstall and reinstall them in case of trouble with.</p> +<h3><u>Updates</h3> +<p></u></p> +<p>Let the system install and upgrade the packages. This operation might take some minutes depending on how old is the Optical Drive ISO image you use and your Internet connection speed.</p> +<h3><u>Upgrade the Ubuntu distribution</h3> +<p></u></p> +<pre><code class="language-bash">sudo apt-get update -y +sudo apt-get dist-upgrade -y +</code></pre> +<ul> +<li>If asked to restart services, restart the default ones proposed.</li> +<li>Restart the VM when the installation is completed.</li> +</ul> +<h3><u>Install VirtualBox Guest Additions</h3> +<p></u> +On VirtualBox Manager, open the VM main screen. If you are running the VM in headless +mode, right-click over the VM in the VirtualBox Manager window, and click "Show". +If a dialog informing about how to leave the interface of the VM is shown, confirm +by pressing the "Switch" button. The interface of the VM should appear.</p> +<p>Click the menu "Device > Insert Guest Additions CD image..."</p> +<p>On the VM terminal, type:</p> +<pre><code class="language-bash">sudo apt-get install -y linux-headers-$(uname -r) build-essential dkms + # This command might take some minutes depending on your VM specs and your Internet access speed. +sudo mount /dev/cdrom /mnt/ +cd /mnt/ +sudo ./VBoxLinuxAdditions.run + # This command might take some minutes depending on your VM specs. +sudo reboot +</code></pre> +<h3><u>ETSI TFS Installation</h3> +<p></u> +After this, proceed to <a href="#12-install-microk8s">1.2. Install Microk8s</a>, after which, return to this wiki to finish the Vagrant Box creation.</p> +<h3><u>Box configuration and creation</h3> +<p></u> +Make sure the ETSI TFS controller is correctly configured. <strong>You will not be able to change it after!</strong></p> +<p>It is advisable to do the next configurations from a host's terminal, via a SSH connection.</p> +<pre><code class="language-bash">ssh -p 2222 vagrant@127.0.0.1 +</code></pre> +<h3><u>Set root password</h3> +<p></u> +Set the root password to <code>vagrant</code>.</p> +<pre><code class="language-bash">sudo passwd root +</code></pre> +<h3><u>Set the superuser</h3> +<p></u> +Set up the Vagrant user so that it’s able to use sudo without being prompted for a password. +Anything in the <code>/etc/sudoers.d/*</code> directory is included in the sudoers privileges when created by the root user. +Create a new sudo file.</p> +<pre><code class="language-bash">sudo visudo -f /etc/sudoers.d/vagrant +</code></pre> +<p>and add the following lines</p> +<pre><code class="language-text"># add vagrant user +vagrant ALL=(ALL) NOPASSWD:ALL +</code></pre> +<p>You can now test that it works by running a simple command.</p> +<pre><code class="language-bash">sudo pwd +</code></pre> +<p>Issuing this command should result in an immediate response without a request for a password.</p> +<h3><u>Install the Vagrant key</h3> +<p></u> +Vagrant uses a default set of SSH keys for you to directly connect to boxes via the CLI command <code>vagrant ssh</code>, after which it creates a new set of SSH keys for your new box. Because of this, we need to load the default key to be able to access the box after created.</p> +<pre><code class="language-bash">chmod 0700 /home/vagrant/.ssh +wget --no-check-certificate https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub -O /home/vagrant/.ssh/authorized_keys +chmod 0600 /home/vagrant/.ssh/authorized_keys +chown -R vagrant /home/vagrant/.ssh +</code></pre> +<h3><u>Configure the OpenSSH Server</h3> +<p></u> +Edit the <code>/etc/ssh/sshd_config</code> file:</p> +<pre><code class="language-bash">sudo vim /etc/ssh/sshd_config +</code></pre> +<p>And uncomment the following line:</p> +<pre><code class="language-bash">AuthorizedKeysFile %h/.ssh/authorized_keys +</code></pre> +<p>Then restart SSH.</p> +<pre><code class="language-bash">sudo service ssh restart +</code></pre> +<h3><u>Package the box</h3> +<p></u> +Before you package the box, if you intend to make your box public, it is best to clean your bash history with:</p> +<pre><code class="language-bash">history -c +</code></pre> +<p>Exit the SSH connection, and <strong>at you're host machine</strong>, package the VM:</p> +<pre><code class="language-bash">vagrant package --base teraflowsdncontroller --output teraflowsdncontroller.box +</code></pre> +<h3><u>Test run the box</h3> +<p></u> +Add the base box to you local Vagrant box list:</p> +<pre><code class="language-bash">vagrant box add --name teraflowsdncontroller ./teraflowsdncontroller.box +</code></pre> +<p>Now you should try to run it, for that you'll need to create a <strong>Vagrantfile</strong>. For a simple run, this is the minimal required code for this box:</p> +<pre><code class="language-ruby"># -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "teraflowsdncontroller" + config.vm.box_version = "1.1.0" + config.vm.network :forwarded_port, host: 8080 ,guest: 80 +end +</code></pre> +<p>Now you'll be able to spin up the virtual machine by issuing the command:</p> +<pre><code class="language-bash">vagrant up +</code></pre> +<p>And connect to the machine using:</p> +<pre><code class="language-bash">vagrant ssh +</code></pre> +<h3><u>Pre-configured boxes</h3> +<p></u> +If you do not wish to create your own Vagrant Box, you can use one of the existing ones created by TFS contributors. +- <a href="https://app.vagrantup.com/davidjosearaujo/boxes/teraflowsdncontroller">davidjosearaujo/teraflowsdncontroller</a> +- ... <!-- Should create and host one at ETSI!! --></p> +<p>To use them, you simply have to create a Vagrantfile and run <code>vagrant up controller</code> in the same directory. The following example Vagrantfile already allows you to do just that, with the bonus of exposing the multiple management GUIs to your <code>localhost</code>.</p> +<pre><code class="language-ruby">Vagrant.configure("2") do |config| + + config.vm.define "controller" do |controller| + controller.vm.box = "davidjosearaujo/teraflowsdncontroller" + controller.vm.network "forwarded_port", guest: 80, host: 8080 # WebUI + controller.vm.network "forwarded_port", guest: 8084, host: 50750 # Linkerd Viz Dashboard + controller.vm.network "forwarded_port", guest: 8081, host: 8081 # CockroachDB Dashboard + controller.vm.network "forwarded_port", guest: 8222, host: 8222 # NATS Dashboard + controller.vm.network "forwarded_port", guest: 9000, host: 9000 # QuestDB Dashboard + controller.vm.network "forwarded_port", guest: 9090, host: 9090 # Prometheus Dashboard + + # Setup Linkerd Viz reverse proxy + ## Copy config file + controller.vm.provision "file" do |f| + f.source = "./reverse-proxy-linkerdviz.sh" + f.destination = "./reverse-proxy-linkerdviz.sh" + end + ## Execute configuration file + controller.vm.provision "shell" do |s| + s.inline = "chmod +x ./reverse-proxy-linkerdviz.sh && ./reverse-proxy-linkerdviz.sh" + end + + # Update controller source code to the desired branch + if ENV['BRANCH'] != nil + controller.vm.provision "shell" do |s| + s.inline = "cd ./tfs-ctrl && git pull && git switch " + ENV['BRANCH'] + end + end + + end +end +</code></pre> +<p>This Vagrantfile also allows for <strong>optional repository updates</strong> on startup by running the command with a specified environment variable <code>BRANCH</code></p> +<pre><code class="language-bash">BRANCH=develop vagrant up controller +</code></pre> +<h3><u>Linkerd DNS rebinding bypass</h3> +<p></u> +Because of Linkerd's security measures against DNS rebinding, a reverse proxy, that modifies the request's header <code>Host</code> field, is needed to expose the GUI to the host. The previous Vagrantfile already deploys such configurations, for that, all you need to do is create the <code>reverse-proxy-linkerdviz.sh</code> file in the same directory. The content of this file is displayed below.</p> +<pre><code class="language-bash"># Install NGINX +sudo apt update && sudo apt install nginx -y + +# NGINX reverse proxy configuration +echo 'server { + listen 8084; + + location / { + proxy_pass http://127.0.0.1:50750; + proxy_set_header Host localhost; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +}' > /home/vagrant/expose-linkerd + +# Create symlink of the NGINX configuration file +sudo ln -s /home/vagrant/expose-linkerd /etc/nginx/sites-enabled/ + +# Commit the reverse proxy configurations +sudo systemctl restart nginx + +# Enable start on login +echo "linkerd viz dashboard &" >> .profile + +# Start dashboard +linkerd viz dashboard & + +echo "Linkerd Viz dashboard running!" +</code></pre> <h2 id="12-install-microk8s"><strong>1.2. Install MicroK8s</strong></h2> <p>This section describes how to deploy the MicroK8s Kubernetes platform and configure it to be used with ETSI TeraFlowSDN controller. Besides, Docker is installed to build docker images for the ETSI TeraFlowSDN controller.</p> <p>The steps described in this section might take some minutes depending on your internet connection speed and the resources assigned to your VM, or the specifications of your physical server.</p> diff --git a/public/develop/images/deployment_guide/01_vagrant_box.jpg b/public/develop/images/deployment_guide/01_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a5688203df81289686d01307d2851944fa2c9c0e GIT binary patch literal 59846 zcmV(<K-#}jNk&G#=>Py%MM6+kP&iDo=>PyPNW-cCjmUCsyKWntO{1!?Z|6T?K134e ze?<Q$z;Vpfn4?w6Z}!#2Z%HgQNz}E|$^-lbyhp)b{_y|}71bzbd~Nyj3z*^Hu-bg{ z`zz3#pJP#T>*y6<X!du=P|)0}Odn{f?WO>ZuP6|04Tm@cPNWOU#zSuc3nGdDdf<5g zpIM(In(d|_DDQ->{2Z%FHL5Aqrg5b<Xk1MJM44yjAj#Up*Fb<U3=9C4HcEDa^Bsws zk`v~_EsJQ|lq3BnH}FlR7deyp#5g_xEDTjZfy<^>ZQPL?KNKQ>2<Wz~CGq1msY$x8 zGu@CbNz`aENV2U25Kw8YW&cuC<Mf`3%f<geRqfL%%f*<a)N;D%d+aNHeXGIlstFX= zwp}+%U+qDouMa){v6Va-omAv{ouCj%k|a4&Vt3J^t<W0M=Kl}knAWy>N1!Rh0gfX{ zj-0R#XRSE7Cjb8yf)*dpCPf+4JkeP6e*)kSB>=eK0w5F<2!H?pL@3p^*72psX^+!3 z?FImWfcCPMtR->Qk~neV#MvToT4pVY^PIswvU=`u+T&Z#ti!ZNnm!0z{^|j2k+nR- zvn|`Q$7OR5(>84{?5%`8KznJKIB{AgPRpl{nmF)$BG~JZ2H=GV1S%6C0KA+y@Ztyo z?WJWtYU0F+Q^OYVLkRh)OV5cHN1!wj2%(E+#|r>xqkZ_O`KXB#Cr%Nd-FUmxOylj2 zCPJXLM}&5ofp)Z`ziL2eM>`s5pbK4uYJ_(4<&FlxrK1?o2d%W*F@XTp{eDQ2`~c{v z;-$O*fPK`)i)s1&lfHh*FBD$9JXZ1LaNYob+N-!wXEtZH{eR=7P&?|9c6fx|Z5L5D zUMl(`|AbMm@!NMM$&w||Dwkhcmb~bwce_|1xosmzvh4c*r<1DgPS4ElF(M{F<3Vqw z6~omD@Z{A}FX-8BEw+sw-vG#4Jr69D!fqhdS%8M6o1MfCNS3E|X$AwS<5C%aK|`tW z8<Omplf>Yvfvx~agnHj8xouhyz`?CnwT>j|sqyr8xW8nVv1{``Yro?E)%6Zr@`L)? z+&SS{+kQnhhB>E*1N{H@p2XQ)sk>IIE32!k)w;FIVs&-3TB+`~Ze6UduC7+A>vr39 z&*$^`)N|dQx98*D>-K1E?OLs_R#(?u54&z%U0tlMu2ySnu~<>7)oNvD%>Vzpt{h;K zi>D87AD9b;JTfpi$t{>+OmO1JXu{<3^jvu)F!%rpBXjZ&Bgil&9CFK}KYS7enT!bz z)F6qIL2$wik--s#+%gJIGKdUg5W>i4;v^z4RHB1Jgv5tY@JSv8Znka5p7Tdk{(qO& zww-iYB2$=Sq~#R0ZEJ#b*7r${ZCkaKvG(`>vryb61p0I>6`~;kBJmc%yuAVzB->J& zxr)Ipa~~kMB+m!>!)N;cMUr$qnKd^Z7BjOfX7=@7q&I~o(;Hw$i<w!6cJHZ-*sZGW znZ4&2b?6gqtSQ>~$1nbZpC@iXN5wakMv2@Er6`4Kj<BMHKgoR}Dpt^u!u0gu=+GU1 z;fOaLuSMmFnbIqPhwV(X<GZ$PW81cEt8F_@T()hjUjbb?TrpfBT+vt=aLu=E?#I@t z*0$|zTh=<y8~^`DqOh3;D1Z`hRFZ>6QxV00%=!J_vqBba+chlhLr6$!>(Tp$1V=r- z@KOK2&T6BX>xN(F;tnAMcemh#;O;K*5;x-R?(RZxh$F$>-QD3Dcjheoe!rPpE}Ufk zdTrgEGo6)L_Zp`=d!{{f-FK@Iw(jn;uJv;2w_T~Xcir8!?(83Rg*0r|eYZ}s>*cm> z)wXS0OF5T5`skDA+RV(%<e2yV1+zB0)t1bRnzx&+?Xc}qo0<1k+cAS;8q{ZRCHu9u zZJ7i~vhMr&%srSRNg|P%)9p8pl@h0?ln23jN_h$?@d2d7jQ?*^GBYzXXPSLaz=9;% zX){L>!QR6~C=^zQ|Fq5jx6E?f+|YbrMg!7FD_CX*GliL%^A7SX5BUW!Kk}n6bF$1# zTER-=9nolb=5W(ZRhm9$B_}W7n_1U{%#+TQJ8+pvnJTNR>vGzu4#;azx~ffGUfzH~ zsZJX&gS??VWtwVFpv|zGsVU2oHjY_kKHIf>!X$H7rOR}}oQy%9Yz3EXTZ$w}y7xU1 zQ8hI)_kdz%re<cQ@$}q!^(QkkC6iaAgMosGyz57{ZP&IXN$P#<dlh3<UU{uQt)i6j zS_6!+s=;^fnLQvulH9f#WPo7psS3<N4lmu){=ae~Hz#&(WO7$I=Wvztwf@wcC1+pz z%H=y>=_+!D0in^k&p8SV_J8o1R&8Sgunvb^ho~AIwLQ>G4bkgm+dk{I_i#!O>>?-? zlrYn4wi2M)X^*Qn9<nst+Tw5v!_^e1z@GxDrxwr{6G4@QzMuzN!*v+KnpQ$902iRs zk6XKJl{r)x1q`-|J*uJ|Ns=I0@u+zZG(dlyP=tTpb^4Yh$&w_=wunf*>ajn$+|9qa z@7=B(j37xe)N9*&c>f_^1*zy$bR|e6pgy04iLLn-)CMas0Sf?`_$g-K3;Zg3^A6rh z8u&ba$p`so-pxDtX83!4k{@SM_-k_gBHS|%-vt2sl8SM`;)N6#tjET@2^jWE`~r^$ zfLn*X0U!&%&h(_`BRrWG0DsIk@O%>A%iBp{7k-HMGbO*qQ&|vzkCM^?)+8f8fWT)v zevS_@5hyHJmkoI{;~}@=cd7FPPJAZM29x9eguVDd9tWUs$i{`t3Ny;q4+n#W^BD{n z3}8?RNTkGw6ki?E;tc}heyI64U(fRZKFc4$S@M?2nUl!XKySpTM}0o)FVb&Ore$w- z=G*x<a-}r3&}_+X@LI;F>sR;{eik>@CoIMINxl)kzhF9^$R7MYFX9<&%UNrXkckb6 z6_v%@{3c8BP1qZ2ZO4ack?;?!%v*R0_I*@75o|<48-*Qv^7Eu+HX>IgJzY35{yUih zljAdS%VRSmN6kqpuIlhC_?jJ(uVG<O^%+o4x2j1?BBhi_@edZTV>GH+k@)!mLOw<Y z9`V|Vl)q&<imZ)MkI{%;@mtUvDg9S$N1K_L6XTyUDe1A$7(YYv+)J<nC;l{UWMDIb z$7d$yBDDDy>`0s0`8Es`-hjLM&&NI(jq0*^I;E@r9n(<gTVXrOMOCTpihXgF&&0fv zwXkjxp)Ai`xaP}D#jsj1c18SOOzi&0e;t`9S9pAi^$i=;!O^PK;pGJe_0I;762a_G zN~t=#6rIcBb+nFmp|`ySJ-JQJH~JRzS^rq|{Q&6WPe+f#>1b12YM)}S12`Rhwtf-H zaux>l9xwHQ1TZSP(uh(01JVK>==EQ_Zk|nxRjZ?yI0puU;zxsWrl;OZDdniG7@HU( zE_{nca+uZSzKEsmNB~1zglifEqnz>>Idlzn#To~)w}jDP{8u)jyjlV&jWVi9m|Jv+ zF}n)}gLPc|P<NzMc8oGJP2xQ~$W9X>dThY}MiXxJ`4{wLuR-eLo`eGiP+Z$?ko{&4 z7y@SlOfVW7S-X>6C*sOZjjB^Gne01+@*E5XFQd_DOtj9<gO|q(QJp13pPdIo;L895 z(kRL96OpgNU@#c73+*BV7$s!)*@-|BX#^_;lfyq^v2w61zu0fBAZbZuK7FLhgpB>9 z{Jv<5k9D9?%ttk?E+eZ7@Ef+n%gwJ_xqxvJxoN{EZRA1*;A5Ib-)=q!KzjXA-H^8# zt<|voW4nJ2mQ0$4O+hNq$0kpQ=D#^m?1<55w4A=YFLW7wwkB@_Pvg^qw!BaO+e_pt zBIZ-PM&|^WM2~N}HGhLKMC@joc&UB00r&wmm6?-C4wg*Tx1Z-V@^~Tn4|OhmmI2K_ z6w&MTVU|)}tgqE>KW_Z+ax*i{wIQ8ahfv*XZPEY#W|!IVv+G6z-$8(>Fv-EP$?w}D zo%hkN${*-6bbGYwv9z8$bPcOtLUtA9^4+H-BMYm`Yfad_*G7C%eo0(X>m_sWWVJcs zw`CuGRR0^ltDmvoWl-4we=p1G)AgD9pH$~TYC0`wl&=3M3b!Zn3A;J3GQ3Hb)b~gR zRK_CyRh9;5nOUv|bvC5p`}L7F+wf6S73;U_ck@+UQ9dfDatQox9asLGj|l1{EqQqU zini+fhqnJe@OGokmDG?%qve!o`6K%Ud6TGroxyP%;U{c5){^LX^{4Dh&EB${?U7l& zO_$P_=<mo8FSpb2=dv^7ptjMQ^uAi>0=Kpmw@2jqp9(j1sEtK@f<^WJ-Be~`7QN#Y zDs+0Q=HEfqlj$@NCgMAETV~al>q#3304$&`e<NqSRG+2mizrFUT};X^Yb(aSQR-=N z+{(mqdd6#iMq@r;)XN+H=RgLG`X4_m{G@Hy(eVWarq<>4JCFx%EB|dpwib~MuohI8 zv+4LeC;_Q-Md2nv0tBjz0jZ?la{Axpncja1QmUOMXJHL~v4jBZ&Wnu4U3blb%5e{t zG9fKrtFs~nNm}mE@2h($IPD<g5~=}(0*SPob`JiSepy~BD)wX`_;S()7D%H0_vs9p z8p9J60HF==cM?0GU|${-xoQltv677PUR^*J;{W+I*+*8gBBv^;_?(0Q5bZ$*`LLZ> zK`9YJ21pVmX}QBA^65BI<o|o2(WW2E>M8itWNA}Tq_La)lKmgQEI_@2zpVeDACjw9 zw3=;{XMVL_du>JSW>a1YG#LZ%2h!r*)_o<J0HM<aikCoYKNTD9C%<V$Nm}ywEuBfX z=k<nPvjHE|SBVxElgxr8wZogWQpNY0C`iSADA*4DX}jSET>J$6tQ_KH0HR3}*(m7# zC-y~}BE09NcH0a2&A54thtQaDM9rwr$lSW3xIY<THo?rPZrVMCq9Jul9_QzJs|@9u z59?{#9ERof!1h;ldJXvsegO|GV-%D)?tAsO?dmcG6Y2-eSQgSZYFey;t>G;i_J62t z6hsY61KmZa-S%Ni`Bwdi^_~L|7-wcGUZsuy1pxT4TyP^Ga@tCMU61&v{XA7ZV!niy z2$Hk}#?ai_^-C<R5xSIKTP1qb|Bh-}d+2%EGHOc<BVMWbueh_%0QfU`k6!zHeX8bC z*+>8$4BLhpLey?QA@8>dTk#9>4n<)6tL7bc5gjl>N!~sHh@9TX>u9?uHJ+gViIyQq z(h?f);g3Z`AhGPma}5CcG60}nX!Q6o5!)#ET@iWyUqZJYuP~a7*WvjFfZXaC++0TJ zw5y2J|5Kd9W}GEV(lW}IaMoH;*@IqnF1{DZ;(L=2@x4crAE3@85D^4F7?S-3-`j(y zAmH~o{%b-4fLIFZk!vnMu7-eWH7TN5`Pi`0a9c$LJmbDnY=A#g8y44qK2(ewXsX3z z019wh!52vSY5)xOY*aZ85##6kWq`V}<WKFs@jP!?kx#&~I#gCM^$zt??gHQfJlBFs zba4?tqvL<gI_jvn(0*~C{pGk!L;1zF&|fPD01Mh4Tc@+aRj)I|?b#ONoPoIM)opW6 zn=Oq7K`Kk_HGKx{VmSgxw{`M`@jRoAh;9yGujPT*G&6f(1YTFX=xMpE1;<cq;4%Tk z95-@q)!rsRy}0@D+u1Ixh@fU#5TGO%7CGW9SYbk=$)(#HS@F1XN0Ir7@e1M2*U#Ez z1*?&3ks4XhFL2WcTwwhcH?XRjQADB~L2SCJBhYZlabI*H`e7W5D+GAV^Zuo6+Asnj zyvwUZz`%`|r=Bv(Uq25Rqq`_=aj{?UiFA&V7@(ePP>>Eyb#Y#55mSH4V6Gq>By0s| z*6O#HbI}z5c39p_q+eshV%F^Mz!euiu*JmzICEq1B)B_pmtb{KHQPKyhyt2$=lB`| z9O!(hZWYtII$x^YMs$Gi_@Uz==0-IK(SbzE_BJ^~I*<5y55XN=paAJ^eoYysqe5tv zX9hY1l%5L%fB}GA`;riCCU1j_jR66DJXbt)afp$McL>Yd+{iKFe6P<Vzj?cZX#<}e z>84C3=Z7|HQhEKz4HCK{%?uVXCBPwqj2r@oqS6g(>VG!N78ko7o<cF?xZ?njA^_ND zL^q4rq)I0UfC$KnfZ?S!@t@XSpab#agGpwPRB(eSh+%7ci92!g`OW=}_N&smGw2FL z0?KJtx=N!VsNF314b%Y>gD5acu}&Ts;UxQ{TPm)Pc7*B&wBGJsa&$xtD|F-Jiz zA%(8&gA$4Z>r?s5nf`dQ)q)~Ncq1r|5*fmA2|wEw7nhHZ=V5dM!O0<ChK@+fdg`)w z<&;4RkU#_*lzQ6t>O|5CY@@JEG3U*D0f8b$EMV-kFLZaEK(WX0EiM-9%Aj4pQ#h0k z`cstD-HoR_+$z^e_d}77aXgg-Fc=^pvi#;eD(?Z}AV30=a%et6J#~Mq065;lX+#{R zS%N|&BaRBXZkrpGFUP#dy_PyO3hV*ZTvkUZ!&22|WI?jXb0##FsSwa;JrB|GL+BY1 zz*Ihj0kA87pEmc&>{J5NYW(uB;AN?mL%}$7(4jj&7&!T6i;Ll{${{|FvUju(;$8(( z8fV6*%Q0S84S+t4<59YzZG9O-I^Ewp0GrNRvI#O6w0IJ$oQ7HAt)w6V)#Q?)czJWG zioo!+#l;Wp&fR4anjru#pQfcK1$#!(<uR|<Vo{R-w1Oj?r1qbEziw=IjSeiSMd#|P z1s#V=7keb<!y-H28086k(1K`-i~aGr2K8|$(E~`QZbh?=cIoEC&BcY#$^dW@0866t zaly2l4|XU3>^8u3^D<kB1xtN)3O<vj`<eyPy&6gMJ=)eTi6HP8Fy34o6QD!B!??%p z$d|K$NM+zuj*$QyApO+Sg2zGaiCtj@c><Mz9iVj&0OHI(Ln8V)mJ04jVp7De(Yem6 z(c<GT)-00ZjUV#g!KtPxj%PpReK_ngL+7IyK4dd~VLr?0izi>^Inepk3gB!GD$D`s zW>=<R)6p8B<uk*E-dBrnH`emqgu?95QE!%&eL6A3?Blq_jr?~R2G{4IBL;V)QMyB& zeDjWP+_9`Lb$rvwchxtVpXoe3>$^Ys;D^36zj^bm=X}NRevAti;7AWC?YrYrw{(4u zZY}Q%UI}Q>Xwe092I5dv34>-BQzNEm=Cs8{1;`U4sT9=-0D!Wot#Mh|6t1wb5p&_) zaT>`G+b7*eN!^}K_xWz1<EKR!6otGm56}uzs?tekEGikbEF=5(*;?rk0h@L-eL?2H z3yNLMx1Q9((|?zHLh0eOodqF;faJ+ou~N}0)=nUbG`$Ipne5&?*_j1_p^DacIA>q7 z-cS+~(LB&0{lexvJV#ab8juPED2^Jm2Fqld1zoXX*4jNId+<(~z`-`^yL$qFL*c*c zoO=xRz&`Ju2U}cI_4#=u$4o^4Ad<QoSL;WdxfCEznH!GGY#gK!+g#h`HmyJDw9Vn~ z1l|^|<J~WB%5j>v0H99P8i2H1rnVMK)!GC89<ID@OmTn(+eQFPc{jG%q8z>+AtXEU z`HLd!`^oTu)_)m;uA#YnK}DH9Sw||%k!v(LjkaixpkoJqsB{e{XD6JouG(aT%}+<1 zUYQBq>sEKRe5or3yW#7cb)9)j2c>n3y8ys~Vo6&478u#xK-UfIPBS*|W#*y}p$OW= zC_Q=VJkpf2od-WaqsQ`sX!I^E4(x8qyp;woXW%t8m82-!a)3&7o$}Z7(g9!X<qYM? z;BX{@d}2n->p<s@h3~Z4*L$r~A?@Qry}Ryq;L%2SN;1<fJ246Zt<G#U0G6Ryax$C( zjEc1@-fqm3-R}zuHgFOkH$a8pVC~0bL(!Q<5b%O%{N6PGgk}eJ6J_5*!<RGQn$qlN z3(q&B1VCGxV|k@+c5#NOGVetUR*xvUs&XYZrfWRF{}`u9o6J{muU%`#Vq^_!;sxiL z)^0!5jj;lz>$;*UUVKY?xbOn89W*7(Ajz@$#<6gpTnpi#4$2av7QR-GMRvb$AI76( zPi=<$uhM~pzi82W=Xv<Km3AjI+$MDb62g@fbt4H{0|3PgsiyXrkYXIhchB$U*lI)I z>jRp=1vG*65;Rc6A#3>Wh8M-8ztZ$QHveSI4(uw*y@f`vKt(e}99p>-0V0~4)#inK z@iXo1>dRrxm1NQ1c+x`4)wa_XC!*D9QI8kz=JTC01$&&P_O_X#JF4O)Yb@K*x0Yb? zg79KVpklzqg=oh$Dv>c8BjS<vy*M`qrDL;a62hH{Vo~mp0A`($vTeBz?HD2((hFO~ zQ&ifzFf&_xSX<(wKmh=<C3D|GDXd+Qg~4!scdv0}S{rOFuN-aC^x?*VLyk=Jg%`F- zf1&AnZ1G9W4(uWx(D)Vfzm6oyldhtL2@w3`4%PL06l+0un%UXieOevys-klK>OD}C zezxYkKWJ>uc)|Sv%PcWXF?+)K?$~BeBAr}WGlZ-{pBdHp8XcT7{Uuy0v5MAxOEYk6 z9`yS3JwD{^(9PR&wcZu13Z>S7WCe<ufVGGBQjP&*+MeB{%~hg8z{=gl!pjKIGEhzY z#Un8B&px>2r!+gTGn9WbO<sXy6B#SE``K|)NC(?O^c40aWy~6_aFQ&)+|+7Yr~_sE zm?!qO=h|T+Rm>f2k$bgp&lKZC%8)%-DyfSN=9@HOfbAs1<El+G0^{Yup4I28&8zKI zAx&(ukT3-<%o?tJmR+^|&hpE$&gxmFzepJJC7}>{tM!dO9_DRbfD?_UaDrxKq*%DQ ztfLsF{%h`x$ncp0pI=yKUf6!DvB(DVk^-_PUDUefbI(;K(*MiR{5Hmx@(gFRND3oG zs*ofA-RwH^e)q9PA^=S^k2L#C#|JQmmR{C9$H4>7lxN$)BC^aEtYD30qwunE%(!-- zUf9sI5dWMZrY1oWt7Vv6KI*?)Em1J`FFbJ2<bqE|(W_oRpcosA1%?T+5CteDGV?e< zNYq;6o4pKMNFC(hy;?3l@=}?Wp2+uR4%S+YH{e?w=47an?RnQ^Pw_Z`rgoh~6&o_G zFkqZWsyLjmDT~<g;x^$=epX{s>;(U@<<pT=l03%4qY9mXA@K0BiKjf!=8CUpI=nDT z^u^O`ndOz);>ztrIH7pUt7z!95ZTE$HFFQFR_h@LNnO;n`ICxGD`k^n*YUnE_=1gY zUoEbbq={WsAw6kqq6c`ukMx%?oyH}rG}f|#sZ-r*S|89$Z0%W;;hMHJb!u)k9UUk~ zx)<k4Lcw%7Nn{Mcv5nZY<}PEq;SSTb=De)lBPuu{05AR4eDsHoxBp!raJoFUMu{{S zwse|#Y}^yzF@HXZ7GSqR2m^3Y$z_>&-r39~!0@E0dZJS<u@57h_dCK`?e@jC)3rS= zQ;<DMD=3momM589F=LciEfaCQX!}?#pTDBx&UpJmVlX*{Drdp5NT`YPwq3;T6u4&A zgbWNWN5E#r$FMDKnF}-T-8$3>fwpj6a%pkhVKESI2Av4JMi%+qrI;zjY$;VSYfl^p z07@FWfiD9;E|viUs7&ZXTVq*GQEOnQdbEpqSd0Y&HR5FqEk7kTMU#87)zd0{epkHH z(zcZXo(tq?zXVvdi9vWSMU&9T%FVIGy)Hhe*k~m#p<t)_vOjceI%!?mLfSx)P;|NJ zYm>$nHiS~T^BZYnu?zu=JGf5*07v+IpmY;6Qmw5xFP%hUa)(@u%#or=<=a=iH4_k4 ziZwgt2qim^KNel~gG4+MKH>%ugDBx5T{}UR(9qF(k%OJpn-@{8Cm?9r*}865HAj-E z8y)q#*L;7eM7!Z~ohcZewJ>2c0Pd9V>67Rwn%e`%`$7V_<5%oB-Y2<Q)`&KXSSdU4 zSe9Z};0~9*ie6n8x_FE|eVFw#oC1P*jfCD0=etUX*>j_>U<TV}9h;}P%NX3zYpyvK zyQ~z(8o>(vlb}R8*a0Cu8Q8SV>evlms<Nf%9yyfveB;lOj)1K%tQoAe%+F|wWmaiz zYMaRQpq2_R!+b7;74MTl*OV6Gj1}7b>?8@`jpx}8W`rD~22TLM1^G{K!?9w9I)?cx z06bf@t`K;|bYy@UmjpqOfS`pFgpdhNGz1KA!#Frx`a)av69DUW;7R5P3>2+IA9WPP zTR~NXr9*|Eq8~309iM{v5=!nhP(8~W?_TT8oz!hN>_4{gP-9tE3ZzcAt88hLLxK<z z0brQ&iDJtu0XxO0VzpQ)v7^Z%a3SdqF`S!sz}wIgc1}~H^&EB7J#TiM4y+uGZLW|Q zLh~6lrn&%V?rL+pI$NA)H>I;Og%C4q8;|3L=GM);JImeoa#5^TH|MMK&N!4(Y!3<p zd%kym4iFHb^^=vV5J)~Wq!$rrL2ubL>JlG`rDpuYaV8MhtNf|lZAffs>k3IddiZTN zA8U97LIzapdNh2Ef=Wq@9y^Ey0DhvY`fDN~LErR>P*5QwRSMW60Q6NiUY6>eO0A^? zmFQtHD&I}4-Lde5^*divby=!6!^KefEiBx79J-2jw(PR4*`|yFY~|1HF}?SXKrcrX zqOFj<fJ@cP_>{mXGFc>PMi7+ARAyjY7PE0t0LRtw8x&=t?!rlZBDDE=y&TorzH5uF zs&jJzB392d?d}+M{7F|)mp$Rn&9&9~_%Ky78uf5QTx+)!bIJKVpwUwR)2g;bDW#xv z7txtL8$*W{ql{s;Lg|I^%jywSs?*4x;lfne#BMX*6N#`)rKB+EMn($p<-e&ywXTS1 zTk~?z3dt1Px`JztXPY~wiNItDU(iGKKtn=CAEA@l+<KTO@yqZNz;fP+ig1LbeP!|> zR&O)_?CmbD)^d{j9eqXF1hB=#$T7CQ-C{cESqx#rCnk#+<bv&DH$2UyqLSFcvPEn> z8C9BNDKoJqfrzkbwoy`QWF-^fni}iObdlQC*hvSr0BB1E?5zU)<v@4A!7I~pXRzQ) z>a+o0G%*}cg&Pt80H}4@c3}o=d@?)0f$X-Upu;h-fmzpt7o7ZQ&9ib>&vP~16#58n zOX&s`SI%<aqDvc8vczfP1gnw<lxcS^Dz?$^1>cb&0(PKUSIT6Z*xR#<XzhwFG(>=! zovo^eR%0U{JyTt?b3@=uOJq3G(`_vBujFo5Uk-=6DT1-5$Qs`zJPB-PR`1-qtqRye z%-F(YNp9!_lGtO3?w(G$p3cx-5%RoK%f!J^d30}cth>#$MI$m8Dk?4e@L|{K2=&d^ zxxtIO%NR%)<Qy9-hQ$b&FYalJC(bxlek>kdaODPR?hBrFtz_*yH61=Oe1uI1nQTbp zvx?$s1cpeca=0_UW=k(7hC4PMu8Xs7ZP(@&>8!;#qXty=y5rvbJ<^!vCO9(ahA5kU z&sse*LSd{?<b?4$ewAg)2?J4z(eUP+ukxe!%TwRF<MEp|`pSkQOKk9YQ4>^;r+pE} zy}_}g8a$n2&|<o)iI-67qtzCaK1rIu{I%Mnojpu!k~gYuQpWXHnwcKf1Cm8hOIJwP zdTh_w)3b%hlZZrQjXRC?oky5|i50gWtCIqdf}CL{i~q_MgIK!rtxum;?o_o`;zu7L ze3&p+>D1M<)kqmK1<==O4lj1|EZihZq%+HrmP(s=c#33!*8ohU9qilsQ!6D-$dp6? z(AJvwnMke#LSqZt2kn&PY4(UCY%&hVSu0W@JgHQ(u%Q`((J?dOf-AmD2oC~sMt4p} zDpu2>v}aJaJsDS|VCXOo4lD}Ewgjoyr9g=O&O)AaZ^4bdTF*{vUo#0;bl2Q!;-0%I zYRlDDT{yFY#ib!nXEy9c?BcMwa}r_^t)jzT*6_GiPn_CVjvhv%*zHS<XKT5><(%oK z-S97e)8Na^7r4evz#Ib*AYZDj##wtUX5lOgc=1z{{0=TrO@&xA0=#0Qw%&N3oZx|L zwZLA}`v`#_1tP|e0tv9*I07rhH9I1SVqMgk-iz=T;D%M(y{6-8or}Lwq$)Zon@Hr6 zDu~Yg!jdlrAYTLs${9tX!jttnK%HrY-&h7D3H8ROAShD`;|0zX6){4TU-tWq<5GC% z8Z7|aVcY2%c!gk^iq%rnN{OPDGq6$H?%_?=KGY><J1Fs)-s9Em$YhDxTkILQh~Sw} zpv5pW`8)*KPMfuUfB1Kw?m%l@=PnE^w_e+v1sHDYC0%yIqu9@$wmelmZ-D@y6odeX zM>Cj#Jj@<W3g<)|rR*azheCROZDC7nC!Zer+1p~S+^Ol+H5PFSS_a!tw&o|B)fCW- zrcKpko@<K4_rx|#c5%W6bK2C&6+A!{EAM2Zhx}Oh!wxHUtVY9Utyh=1GuU}^1A-=I z&v8dcb=*Gvr&IC>ojqKbkC!HR7sk~i|3k68oAQshVKZJGVAS?@ur01vLkdhZ!g(F; z!Uo|^XNq_9i-uB3r6Yu6*sGVzR32Q*816m}2ub6F&MO&4_;jLYkbvx)w#!>v%Rv%B z14EU(I^*JW2mp+E0;mS9g66|w!95Xm72j8tp0B~`L3w47<BcDZqTa`u4Wb9jj*25B z4;j-7Rj}{BH@tsbsx!|ihgF-?f4ms0=pAQl6PG`<mCa2Wk#NY0p11oC*BQU)g2eFY zQjEEjwV(4VYy@`i*ox+`+R;>7wyt=`wlO^usJ5{2&h{BDS{7)>i4*k!b=`S7Efu!C z=N16~=)UhM*u$IdeMXLRka9kszhNkvh~cuMa%9F^cVuS8O}Kub>rN@N62ciu08kW( zl6BJ=ma0TNMGdn?dzz?bd=C+XI2~8@fj3v|a)Cd-;irLt{HZ4zQ>b>fMkh3HberAs z$K@Wjw>%dHexN|44qsVwI?@(=$+ovfE1I2kW>W>xj@Imrxc^G~#oo~7#ebRTstJ>Y zvq@@qW~3tf|Fh>}?P!1!v6H%(lEoNEb?}a$Pt_4OC-Pa8h$o*0X8yFHo*F5;Hns2O zCLFv)7XmkC0ezj8DOo4PDAXJbV8O}!I2Q`rS^Hc6{$0MKAoq9w`P(Mz+m4nDj=|G& zA-37}8dxsL*TKkX%i%u0DU{;&xmdXQ4ZHOt!3s*={}tOZ-0l42=G?xu-PyA1uT9r~ zSSm>GQe<4o9bhUm)X1hB^zkGtkwZ7H7L-wvWm&o#`>Qg6cq~rd-2_{o37N3&bLJY% zr#K$U@8d7iw{=>f`MSJfsHFvqQX{LI#kfw`+|=Eur}8&tdz$z45Nv7j0ss9c9?#nU z?Dtlh^Hx7kd-H!?3RYez2VU=<QHDZ{c-6~zs4h+@9-&9BXTu%Mj~DIpT6b<rn&9sQ zcYi2=_dWkO#oQs_&V8p~PB-P@ShY)$W!HrTKK9*kU;+Ro2q$r*lL@&fmvklC<i0Kl z87Y}BI@2Q-u)6pnT0%YL%kLCer&ei&lDY;;h>?S*-QbzHr_p3aCT(qpW5<pm*LHPq z&|OEj5AM$C?Ns+ggUJy&9?j=xSDDSc`PXfn81+=ZogUO;yF$=tO-Dksr=4lJlNkem zV;k4>RZp&W*A6F75AWr0`R+(r1jx))?z>8W(kU_S?Li6Zz@QYaEYz)y;s^|$b9Paq z(1d^fL;tZvs6H3BK=p6dBNV5ps)khN>=P{bNf-Hg_X6CST7JhU-CNl~VZWBv{-$iL zXWzu7!!rM^b>}iu(Q{mT=xp@@vm3K}tTcPsJ6r94`XgD8ag(mDGf@BJGU3b%wG)3V zZzGL39}d4lG*P#s$0iYGijWjuV>eRG9#&oWc9iqLNn=ezo#v6NjCjH^FLrE;cB)*K zq%A(vM?f>gq_VVmZuGR^Ly6$=lTV&X<Ye30X*!KGwx&?G1#m*J#(VA1%8h<jMGwDE zSsK+G+uQEBGVE40*E`|K7rO8CZnkj!oZmbq#^ts;Q^;(}1!RU48LOH0>Epi{Y2fM; zw?Bi-^o7ic(w&P001!$k1Rm`vluMC(hui1`0fH42fl4IM`cQy{=BuT84o&>PJf%P$ zDnp9^80?8$O;rPXN(7SX$&T%<^%dH2++nV2AJ&bBLWJ##MEr-ypm<-Y?^VW!?=yU} z8s%F%=*3S=aQk)a{*nVnY<ANIXFcr%QH7Sv#4wnXdp)xDgs(_u?I&H>=w<{J0*tH| zC}Yo5h{TPvBMq3ENc2?>bKVhVkr>?%?n*6Em2|mV`5o8RMbK0t7Eul->KY|bWYwD* zFdoW-4UOsp*zGBWhb=Cm7U;0mWU)>M{<tmu2$ak0J)wKp)0qiob9(;ImHaDKqT5}k z#2%kD?sv5{FPdl1jh<`06JW&ssWWd#53EyFCE&Z`FARxG77jF)nBNm3EISwe<eTGi zUy9I-7cTN=H4lxuJm7kVuIe!FaaOnoP+wOCw@E`IwrzS-HG#wBNuKh#mLbDY%;i`k zaY8BoR5cu6sA^71c1&q&JD&q`TBI6N*zL-b&z^r%_>Ox&&z)QD3$;_!*2DG=o$-JA zZPN8e|31E8{>ct(2sye1l6gCAb?%4k$h>QGc$N%EMH#-s2Cyyto_n2f%Y#|NeR{ZZ z@Ulz*&@}|YpQQySO;i%xeU=AHr0ntt0q6-r9}Zu6THD5U(v~Oy#2k3~F}*l1k1Qpa zKPYV`dHJC5$eHc#Jg{_RM<?^z)!$<G|JUC1i}LmOTjQ5zuTg#V5R2vD@$Sghh(vSt z9ZI<3-prXUL+)3moL7Mk^C)Y*nQwkr%5C=Wv%8}d*B&~1t-EF@o#m*iG`YA!vf1Go zN1QFit2c_*Y$%~sciLaqF&%s}@@ppp{6$L37A=oh&?n<MC6?1Db`+^8#e=a=1=!-c zli7lyXIv4fUp(93MzEEsch{-Y5g0tP#j}y~xOm_H^YFuqvs-uH+{+t#$=k1d9XOZi zc=ODl9qyztlKs%xR;a8w>aY({Q@z8U-S}_$A)dFB!4-lOxSaOFk*b-kRvZ`!m02Zc zgF*6UUIzunjsKSGZ75g%RN;i2D@HET%D`my(Rn15-Kv68c0BG}kA)OzOS^`qX?PyQ zlz5YDn_T*~KqudTmoB;Y5&IlU^Owsb?PLBC-}iuv!NYdXSj(Nq4hJi-ANvn3U;27v z8mpajM%>hxg>)yzM}jlf!SZi+-Rss&122U}_jL<_S}5>9Ub0}G8ae8Q5K6`<%W2_r z03$~Ofn0eJSWc0K>JE^XZ5fBxR1_ILHZWhe>ROV}Yzcb;psC_B`6+hP*ycw3wUr~D z9J<_@=H1RXNWJ1K6me#A_((l3D*U@ZpWll05qx5v>tbs@b;)LyzNG99E(9@kuY{-@ zDf_*SAKRFqZ%^Dp#xEq^$<Y&@y4W#e<>1|`+Q5c6FtFcN(*=4*;E3eWyJxN}se}0T zqdJVy74Xs5y*nh*uku~uHvSMnZ&fE$wG3U!gj!CIAt0D(l%Frq1F39r(c79_-nz{l z6n8!Mn&^yiH4(2$_X@XIAihpt@awnO;(hKFdh?Z^#xZ<{JjnUXkFlG#wvnSdBW>(Y z1~V5T(QR>Cdee_UIkV|r<ZbKEA;vp{m7wntx6FEe@kocgA%we}IpNRQ+0w-VAke+o zp=9K8%0iaqgl%O<heg(ZdZ*_)e|7xH#~z;t&=O2@spet=BnD{oKLjJ&UF&(}7BhRj zl&u3(zij6#&J|gA?Ub@vn0rdvq8E8VdrrS_Em{dS7;=rJJ@=h#_|DP8t?i065;85* z8R4j0_(w0`-R5+70{cn)CArhSZP;}CoYKL{VjAk~_j>a#m0n!S$h#KTtaK5u?8-_e zj-oOApZZFV_gQ29^Q&ta`}ygHnW3V^{K-=H${Cm;O<q$tz!uk?%vNK~UOnf0x!;p( z<`#e{{?4qbR57Qka!R`^;<h=hptg(tK6~kTC2bq^k;l8Q=jnWE#Fb=kKe0sP?v(C- z*+2B%_Ysc&?|}1;$ctw-=<Hmb$QJA11s&){ebzFCJ0mHR02B|2lOH(ba>`_XRSwZ7 z=B*!Zef~>-jMwkgOrx4QpF{Ipje)!v1PLfdtEO0K@7T_|lgWKEftNzw@q6|2?y@=} zv`x|1)kmn9rgS}!v3J5<$DM5qw+6I^nUFe`#+988J9Mt8x-z%7T~&9)B@>ZIGdwx) z9$Qam?~L2J`$W)C@YR7Qo)a^5<6;8iH>*G8AvOK3O-7bu(Nb&*Rve+j#d!AH#68I= zhhi3~{l^iCKfFf}Hk2UY&&vyCsjPZZ*d<i+A((-Om+nr+7xOObhE1{n^pI;Ut2#at zpSadiq5F&4>`@6pU#fzEqS|gf$k)#I79hvpwsqcM+83Z!3R0YCdYjs-v&L&2mPF8< z$lUAhu<fx|%ElDOH&J1;=yyxIRC52B72ND-u6k!kHg1CuPmo1lZHo3;xaab8#2EA+ zJnp;{V>0(cp`3K1Z~A|{Ywcz2;~al^_$M9PH2@6PiAuNc*PTD~+`5b_X-!=>3o&4s zr8!}n=CDJ3S6xo{kb^}q5KDl81ut=<%Q&~~X(S$ihXUfm`xwDXb0=dq(addaHyNwQ z{UrcIGuztPWPDk((Mckigs4ZRhdbR108=)4Dv0o@&4fzsr0OaC^!L7D|JpCd7-XOq z2<2gy%#B8T7a2K%<-|T$n%2t?QRjU_`%BCV#~o$!*-p~H*ZK1I^5d759<5{yt_5Tu zwEK^~cZs^rq+zY_fk;r7%EJPyxU!y={T1?8{zRVvAj)){gY(?Zx`DMgDM5)GyhL|0 zy2z+@>~4Agm+t@o!m(q&unV8-+fOmpI^$Y*wD0c7Tw68psby~0elO8@-lpYjJfhm` zI@-qs5qWkZ=CWt5p2@m<e!G;B$lh5C)M#6h_A$}p>1-UiF$X{l=WxaIdpS>pwySGY z8FHjb*|}S-Y7r~zrTEEzG;y(<tzai{I_BLcqw6xqNJn3d%x=R2@z1<OcQRVB!Mvpj z18gTT%Oz&8dZz6zrZN+eJTVkDG<y_~JXxz1y1$4#WhTk;%eMO3=BBTc09I`s0s7g~ zY<C$ajF7iP!feJzG%ziVE;33n>3^2@w)*Sa{HOH&LlQidcg<%CHgwv^i5MG;rtO_Q zDv&&G&>bv<`DiMh2i9;ROR*Caq&O^Pt(3)V*2M>|Y`1OE=Y;F~gIr6%%!~Z->-qA_ zqu<|EOBaMwHt`PnoYyt34}afq+bg;9eh&VRY+Igh-<Y~!Q@czCMvihJiEpyH&UTDF zy(R@?JcKG?Ox%^n-Zxws>#KI(UIVjQF?LDXT*Z8@+44mDM0qNut5`a!B4q(x0bZgz z8LgtO$)8kgTZv7oyN)dkj!|skT|xEA-K!gPlEgwuH&dl!305F*e-U}gRO|496fsM@ zT0^;;vr%J?1LWHCS~k|`_O<&tMOwsWGk4y%xU68i_|!r&h3RLcc(z$ys3JD}rZWZZ zU%s<=l)*4zPm;t!v74>Z+<-CK3G<PI#$uK@V_X6j1-MX*QjKkhBbTETiSGRWKbUMt zXAZ{7z6oJ2zV16=fZ3d3ZFsVl5b~Mj6SGSe-=p-67w2}=@rE>fnWty%>fzJiTDTK& zmx~Vs;VdyNHCHrEk=wKUu6U^g0KnKTzf?T~={y5-Iu8a_C>5XxNI>xN+{tK1wwN|h zBq_S=``V--VF2>BwdeieV+#$A)D|NK5V*hmv(QuK&~dPk<WFM|@`c*-XAT?(7jkN) zP|O+NQut?)*~}!_z&&&v!vJMk=^XOEt}z}J^1X)I^V1I;Uv|WElxppY0YH#~C?Cy4 z4*)2eyR<AAb!usbnbu4wHp;cB2bhd^;TKm|R&>wlh=>h58|n~|de!7iva>5(AMU>X z^5mV7^Vur{Klc-+`yv6FUb^e@nLz?t7|J!Vq2foRh*joVY&Yfx_p|!0000=mV27?7 zHSE%q=Q99t1_zR}ZcSu>kHVddR$5aC@W2;f>?qo~gXC}~FPBs!@|5vZf(Za;<ku9W ztysMQEKov_ryMR~U>Jb;D)V!`xBS{`%EUd&%#bktxeWp!k&osgMr-LB(lsDQ*tBY+ zIb$-dXHDcD$PlG-HN=E7ZS`Zz{!xFtUFZoZ8a>e~a$XAMrKlV7=KI78<+8&&e9w$$ zlkfO9{%S?AI$<r_SmTU_oN%1!G+)?@W2{}j-*2gp#Z&R!q|3;$snsgGpahME{4~x> z-Nh#n_seLCxQ7V#V~{?IyRKM@EB+bawZYI<0f47MQ@4p`>DC2D3Xnj;Qzm4bW&sRv zwJ*4u3PB-*FZxjRc5Oheu>bVcPD*2Euvov100Npi!hAFrqp6w9?IbeWRgKF;PK#e< zifxvm)0Cwu)Br?nRHA)1Rup+!kZd0xS8_DgWA8g`kLuvUM)*#_q!&ALa#OW4$espX zK0`o%$W_g0r}oP0-Lj=*<^hwTxf>K&u!Cih?1EUz<K`2pIVGYQ<5+-whUiiUWHK+^ zos2$FI8QEq4Ro_9g9S)PX{Dwhu?hAF=)FPc{-VND27vb^g02EEN(pj<0d|CNLv`~E z4e7(;jpnDfGx?$*g;r9Kj|L;AvYLW)tp?QANBPj|Shtb|Cbg+`O&5|HNE!oOsVGyK zOBAf-9g4+lB*c7%=@Gnxa6vMyrcdhdB3eh9*yrc;b8kyI(;-^<2WNKd_p9LmEVa%M z7N)e_09hFNu2_&J7Ugp}-rUpH(>oJ=2NZ9rCTJEf-}1yY=XE!mGFm9zMI*7@^bit6 z3TJ4xQ7vTy1GuK0&1Tc)Kw}W0`-{j^MnYPiWSw~})BNi&dAfYCgxO5=_`+R^{J~PA zk<RL{;9w_C2(3&~`pK{izP7Wo+4L0$c!W5grNcyj;eoTpf0M2OL#<8L002tSq-2Hx zpoly%VHMRH%p9TXA%f3nrX3r1UgR<!z|CjGs-L#!#?5j>Vp%`Ya_yj`w%i7_71wtP zO?G4-14?B907a(A7_*y>Tf6_ddWOxhU0Y^m9?o#K^WLgbQ$xg1h<fobbQVPZ2;9jy z<Broq^~KT7yW+ym;}8as+B%P~?q7I7x~_A8c%(vdkY@y_p7GfN_ZN|;j0ADQR?~F@ zyQfWrh2#A}B*;wGYELZ>cSDg3!TDaQ!HeCXagBl0U8&qn5{QQt!a?>))ss9?luz;) zKHaCN(U~=VsjdO03~8$r*-K-HG{4JTnK%^7%9~P2HrPfJS`CWQH4uPw0zyQQ>$%fd zwL(1m23N#qW`nSkwbQmcwWAF+>5PBUm99Gxn`o=;RE>#xF0BFI!f|GD%t^W1l;y24 z9VqT18PDy^Tn*RrcygwY5~wz$da{_QsgJ~+3{FrgiUib|7J!-NFu`P*4sg(PgrK~w z$o)maQzit~8wcPo9s{fxuhIfig0ROTEpSj>3#oUX1BAHlKhBz^-uN<P3jkn_c^d#x zJ|ST3?0}nVA|5JOvH+7y&8kfnvl&!0$~DVcPbik;V*MD+Q9?-0$mHvpebk<lQ$ufX z--(5JFl;@weS9#x)wRnl*=;4q^k<_2d1o<&-Lx#qv8u{pVGo2G)~=DFiTiUp`<Xna z6zSL~WtyTY%3Y>R5-)nP?Al|q;DTx2po@&0GoaJ65fN1LF4}yM;!qAgg}h}D&&a$W zyZE6T^LxqyvtmWgc@s1_pR&|q&NlY;_<nqOKCgx0@8JTh??g&x`9CG%DN*6OvyZCB zu9>;dkosLzanqIi18^XLQDxVN@=^MMsrT_0YxdsZj<MryIoh*{zagfa{7vgE67eSG zpmo12vOYLRXr2c?^^rs|Pm3vVOj7nFV#LBcUwmcr<4%9V@vN6`-Wsn%%1Ma?!5PB2 z4g+eg${ZTys3MG;YUZ}>VLZcOOcyfST(f2Mlmk38)t_v->00QHDIntzb&c~ljNe{q z{$$Lf8ka`OOg>D(w|SD^&(&A!Z~v}iiML;E8)O4QovQO+uU5|hK^7{Q)$M>AK6W_K z9S`br7zg2Fa2{F)wz?<_4p2%2hZf_~bwnKH9cRs?fV*>@`~R$cY9ps`uEbjq^C~5U zq_&d8bA4g{xJG~M@g%WkEdT~75nLYvK>CId3ZN+nx&S3*mSynY?YcH$u}U#%Yr8&% zCA1U?=|Bf3XsMI)ZfwUmpJR>%=LBh<pK)BIV91!+6lfYWSIEc9e6{_*8$da(pbJwT zK2Gd3y<kWpfDlzEXKG69yQ;=6h#=Yw?C8K7rF_4wMgGuuuBb@42bL8Ke43?XsnDro zM_I7=Inw2^(cf!dVoBsWWQiaG^VFIwYE&Kzes{<r_5kGTo^%HSlzeB{4?vi|DIJk6 z2<?Jh&`47YS}=3B)paM64{?MUisX&jewSiDWt6`TSySE_@lUxg4DZEP9M|lXphC=H zHgc$3Rvwn9`=VJ(#q~}TSM`c>6M?A;BOPTCdg3C2nq;dY;dzxWM{RYJla48o0METT zFK5XPBFcZp+>Vrpd9V5DkMrakn!A@nByLsFyYeRlGbK$?i+TY31RJ`ZY`iq~^3H0G z@$BvpPXk@@DaOq1)7I0jAAwH9ZkvmFj$D?y&=ef-*5W?pnwQBneFmvc4@k85cW%G6 z<J*gCb_6yq-A7QijKj_YY#Y$>mH`5=3&8qdS6-lSZ2@2Z&hN|=u8<Q(IZY#_DeLjU zOV^d=;CvW>^C=Csy+vk335^_5y2NPkbe?)Y_UawVqcJ3WPR#$v-|VSC#GNg)t$29= zuk1jQQk6@^v>p}Vs1Kw86~8Uo0So=tJM=g5IgbJmBXro|Km@WAL%Bp+>T=HxE|GF< zYcrbRiLyKVcTk93O=2<O<Ti3RjIaapT#R$@f5nwfkf%qrYaM39xE|Nac`H#%fV&iI zc+2RANRZs8U?caN#W=@11HSoS@Fn+(z5}2PPRY#kQu2uXMds?f=3&MlnnK++_l=a5 zy&j%dhBSM*--+NU4TmXb!H<Fy!$g{^)O^jtubK#qe4G*#HQ;6dH1u}Gi(L&sIe@p> z^$x$a-#=Icxq+Ta^{YKJDv)C<RJkWiu7NlXZGwoK2m|sK*PTotSTI3p)T>BEgih)m zhox%He-Y<=ga|?>h9V7F0CifSy58TWi8NllAwfv+0(d7&7mr@+w}~8q47#iMPoMT_ zXTlc%`MWOn-|PF2|M<VXMe1(Lf>M|pZxUVRCL{&S#MU-$baZjwm0iFtxOk+LZt{L= zG#!YicKSNQ@QL^&Ztg9T)AXHV43sr3<-AyAaix`q-9N4X;I(Y{ZSAe#*NJJMe;51* zuM>;g>zSuP_{0AA-f#8gr@#LD-~V>seDdX+f2VSnfxvrGm1oY3{gj9<L}ELO-EIek zP|hq{?G8m8I^z7KDN9^1EoE&Tc0QjOLNkE|?1Zub6||rWT%+=mu*UxTb<bYx^>Yf4 zp%!A1G5VXLd#eqgjzh4;>2ZJfpWgY6zWD4n{NnT9`<EZRe|`P*8}In7+}9ksF`Pbz z92h$deQT2M?(d8(u0<W32#>oM!k@=74oWb}=)qec&KQJch(Qgq6V@NLED(sRQ&k>W zf@FBP{-ebYn>^hA+y4<vQPcLHSiEMEJBw?#S-@i-{u_Mu)gSu%51VuP_b=CPzT+1g zs}gqydSE=nD6+<bJcYP?>f2nrW-RG+Bo(P2_|M+v+p&u{b%8oQq=KiyfA{7=06{>$ zzd161v|yvZDc~7k5TX;%0iXl3uF~r8Q4Igps(WNNp3Q5M^|{uAJ}7#CjzJfTVHf(9 z>!q|e+Uj~g`qN{^PyIM?F2m#lQF8aBv(2Ytzr}SY!<m7(q(*n_ks+rY7TM8eK_T`6 z3N$tE7JNn*FvPIbz6A#XD!F6h)8t+`A8urkS|VT2BE0<5j~>^)R?QPY>y`*Vt_XN3 zvgMev+`Ih-Q?*PM6%ojiCbWz*I_~T)rtPddnVfTm1Vp1{)jV|L6bb9^bsIBuB9cN% zk%Gf+CtIpVU>H*`Ox`WXH6F=$KJdeGdFSOy%IoK~IQbU|`e#kprMfJL(7iw!ICqRY zUiue`3m9x^NKezC#b$07JV@TQXH~VNpY-zICCq-#;>ry`t$EExR&LZ`aqeZmaVJUC zpe;tgQv2P4YJz8!KUD*rk%c#kpu^ckA;Y|^=f%fC04Tz=Ah7_g18@!1MX+5O;fMA6 z)FnwcBI1T5MdRi$0g(RwlmGvJ>Dsq4?|#jYj;uJt7PxHo;osi!PBYGRa#CSu9l7Xa zciqc!C!?E;Ie4Q2$I>i&-AD?ptkD6N(jBWBk;a`ZNcka61R?_Y8+6Y0A!=?M4LUBo z(WN-13hJlIGci*X@8*RzWmpV>1WAcz6h_G)DMyKo+wKDP5#!&eVZ+5VKIy~gH^8aM zlcsW$s~iSFGk0Z+>rMs?I;Dt4xf>z_5!tP64Wp`IH|-KKy3g=1AV5c{FdVy;(Ldh= zZ#yLQl73w;;Uj!W_h4LpC3fq1B|SzA(a+xtNjjn}b~__-B{pQn;1#34+*_1Q+0Bm4 z{qGO%7SXSo>dN_YMnH<i<U#o(btf|s&hqG}3{zu6?j$mBqVsGg+N?&Tcn4!k=S(r2 zUIY56HJ8IKV~dKkH3xXE+4*%kn=Rdg$JWEPP<yJ6d12P@05+JHOzAMEm6|U}AIFMQ zOb5;Im@A1zG(+6o<vXAzJftE`adSt&ZKIq<Zmr`D(LLDR9nCBinZ~^Uy5V?Khijb6 zhZrdU4#4|P3ZqkV^iyT>Pl6QIPkk0X7TZjSW#A|jH5MIMP(YHo-eLo++Ob}gKKL_} z)^X^XY(K5x1n~5vh)<=D!kx?n*-HR;Dsb^6nK2EHZIn&RUB;I~B1bxp%@sodr^{n2 zB|<Q0X&@qo54}zHL<a4}U|YUjC84{_Kx|+L^WkMPJjH?TtTJJ?D~hy5GVV87tT*p7 zCY3vb#9GU;+c<uqhUxPnh9**^O0xK{oIEtVbjsYM(x~FHKof<o-~@<#J4~BDnu;p# zfKEpMo|?fNw^reBD?jyH<iad{$>sk9CjGPu0G22MM2~bVP=+uv^;++TnZYD$$a(Df zlmkIEyYu;svV9cpWX?4JP$S}wB^$^eiY`wdB;z3~g_?~y!3z(q)M^p{<yv@6dUowB zUU0gaKb~#wMRT`ghujH}qqzo9A}QQLJ=YZrCI+&_)~0ZySt2c^EO)4f*WrudW~Z~e zR05)=IAN4fd<KMcS<3cjMa{KgDWGh4i;SUY^R$Zw$eowmdH!+~w#svDAc)!GfXm-5 zTm*W(Z%g){t9-}bg?)?#j}QHG-ABK!0mtKbt^%S}cM5gs)BBIWolLGMTnrc}TPc0y zD8O5RCz%r%tK93*@nx6~#7oy$mZQctRJ{B3C6;BF3A*i(<A^t$XFHe?Vu%_%0Koyc zZ94;SH#AGQBbrK%R1a=cb1383#}OU6un3rlrJ@T7A{HELK8UVf9D)A}xT3&B$_dS6 z^Juv+KFlWApd$0pT|5yE!S*Yszz`h*HsPuWg#G>LrAU+7Fn&7l>u|qLIK(3ZLkB;i z0hOYb+2gMCAL(;>^%Wa0Pp{rqY9#7-c`*t_RA`xNIzaStQj^e_KIx&}DP0l**sE$G zayKEdrKzhaQsyAjb<1P`mWqan2LN)f8*3ZAhCbE9W7@~+5u4@IBaPu8(McmvicYZL z*j<TbebUC_2X0`|(_pvOu2E7f$Q4F#`NuxFdlgZ!3=X3Z<o4pqXf(3XmR;w!uirkG zf9Ltpw<F4#XCD;L2rQ$@vxcLh$6x$i!kC7ij1rRven*TWTBE3Tmv`N#t7)mZb_U5~ z6B9Bd2}~7F*=DSgWy1XbA?9bFq)~dc><(am0(9(djYi!W(#_e9{f}3P9OE0vPGVQp zNQxl821q!0r8p4a>rlASY%^w0#WMYt%cpI;ftUSTENIf_qFR5jc(mK@G%h`OOg@<k z9I}<?<cPY*ig*3(6x^@Q4?Yx?97c(qnQBHbeYDrOyO4*j-nc<qDduUV!k)HV*BRRj zfR)19%i-(8A#q|Gl>^5%t4x?I$ux0-D<)auDEHWqyS0;pMO1J{jS#jHCK7KG5KXq7 z>t?CfoO`R;kwHkyICKGJs%wA(aAt(E;faMD(V>Ag^G%yq%up_LO6qH;6$_y|B>pHY zxn`)@%NISzf>3O9C?7t<N3nl@*0zNr#J`eXHvX8uyh(sdl<akLek0L^e^l=h1|VUq z($p2U8Yx4jRIDOr{J$pN`O?%RQvjk6M@S#dBq8DA(b5H&zgBy+*~919ByS>aetV-e z^~ytv=@1vcNRu?RP^>Q`?Df!E>N^-4IIR+-4tg#EK07ocr2u%Y00^`Osmu&(n2>f; zj*tqJWqymvXqv8RhYP9xL*D?K4rmyPA}(-D6iw<{=(-#DoRZpTk@8G`L;jv`fBW@V z-2j!ActVQUzkGKx6@rjmrfYbOXk*M51ia>Ak+2^=WE=qmb(VVIhi4hi2x@!*f=Lb2 zPY2-5K&m2i!)wZd1Wp+H0;Q=Sf)Gr1%UbE}f>`Qyv+MIy2&Wgo)U*vbCKzSXV38Vc z;8QNKiwkL|KwK1IzX>soMM9qY!TwK61DD~?*i}4MtQi8s?Y;A}+t2ad4+R_rOiM|= zt2bUc+s?X^Y3lCI00_vKAMK`#M)}NZe*8*Sa6(mCWjpA1!dIXL&wy4!D49+j0=}cI zcZZQ%0m@7(^bt1QkD-*tI};UMpmubzx)%UChOFcUy0SYeyvt_U`rePdefX`iLOyVI zy5Z?~9!$mg=q$&wiKsWej7BdEarnp6&Mpoj)6N_G$c9rJ5`L!*`;hPe2M9nDI*O7m z=`t|R8NSYWfL`X|E5CZ$CVr0G!(gELdH~g8L2l6(Gf*+6@Ro=xZt|7pk}CND_GxDO z=*JsiPneg(=g{sm#%s2+_D9_R=RZ5<_iir0S?SC&7ANO<5l=Dk96Z&uxU8%7z(CHa zQ}`;nJwP*>LQ?`!riEq!aEJUgGS+ZV;8q4J@}g@^t$ep!0fxBQul0mR!=fY5_krJ* zn)scrTXCxQe$oAe^IzAL#tk3@2gL^`%R=wz-sbHre!16v`uOAhZwP=$XM&oX{kSN_ z%s4kQ!p}h0gHT5<`gd&=cPAoIR^zA=)uy5WV2F-IY{J?J<j57j%!Shogbw^7Llp4w za1=*8Pq}`3a_Pmcw|{AWdy(`N%)j~8eR<!Neg!~+CPJqm0Xaw7&f*{YuRq@ZwE(GA zIv<vCA4^?oo)-k#7G~hSywSrBO2<APcN^LlyJe9^6sn+SGt|s-A4>wcHak18AkVtO zWXCYs2HvDI;PqYz7KJKQDp`<M=+7!9n`<^ag6*Mi&2-@_M&NKH+5-_BFtwgq+gU)% zcGvW=j%uwwT#%ZVlpVLa1_+qZergKHjc`2xJe$C3YJdho^YPdaHg)g_^cB-9m+Bl3 zG1p)o#6k;tviP)}<wd<WMuGMA@KJv5_lp{v_uu9pU&w9QYK_W4P>3nI%%R|?&uxqA zP6mKue^^Fnyex8_FR@fQF0q?yDn_6jy77JwI}2hZ?YJP|DMC$TVU++qn?Yf8z!%<W z6|9=8J<FT`fjJR<;nLn~_qLHaI1X|Ouok}gZpELhRf<2Y*vWJ^zi58-P!t6~uXfU9 zivDubZFi^l7kzy{BnHxDPaO-M&m(Ovj}CAw!vi!b7y@8HaZg+x8U@%0@(iB=ayAQE zDJblfyvM7?Bs0}`+NJPC{}P!4dBJyNgU;9Q+Xa4u%X<cbpMP*p++-VK^NP|w(Dh!S zn!tn(ecu7Y6t_3qT|7M_<uGRdcr46QoD(IdC)@z$uJgozyB`3){+*YWLZdN(#+MM4 z8c_3U7H#k{Y}Q)h%^z*swNUQmZ5IPDrPAUu$Cf5}IvMxb1_#iT^#0uKzkSp2)H?*g zl^?qQebnb(BZ!^op`AEW({6iOW7}&Brjh+jz~gMoh$q^|+B6sd?!&vPfJThV0(knp ze!ri>!PS2nB^IOv&4wp;T&&Mx6m|&U<pf?$u5h6hnrsOGRhw(fL`D#hsxN?8@b9wU zp`w{t^t9y1yYGE4M?d<B@g~^GyY65?6c}T>i>2O#E;Tu^;0_oDpRG<CiW2P8vhJEz zBdM3+0Kd=g{geOWXOK_($$<93g8)<W{g&76{{NpB6!_n38Un#gv1^{CgJ)sh1u!^T zixuEQTj0M97w<>^D4T!b-S7QQF6Ax&z#zMFd-HWMMnY_NG58ZQZ~bN&5I5&K*vaQ; zGb#DN$2ll5f&qWxzxh3W{rvI`CTJ9=ZUCiJ4Iq&T5?il4>dgSKbn-jJodTO(9w!SV zf;f&fz&xGY;~lcm6e#Y+<k(Axx2bAs^@{XWNsngv?dutPmp_zK`o=#20r1)A)&rwH z*idb8-N^vm4M#UP&ICU1jhmYYi;+^k+KzaGrRX9#HR1XA?SI_mi42CO^2MM9-2+lJ zT^Q}AjSC-pg*yQF*<jK!xXTDOrbkLx#PTaGnn}eDH4Sp&lfZ*%W!fT8S6_7sf^@!0 z3ScglB7%qK@pbY3hn@z&0roNO{LE4Wlr3+B<321DO`6ZtHQGKs+T(f0VIRjMoJQb) zX@uYPSNkXY2flCx@x0PuKO!`{2?eEeC))cYed^nH&a37yLpQC)#GWwE+ddHZo9NG< z0qLk}aBpRDZ#5aYg4yZ<V<tyQi?&ylzaC7EDzUR#5EX0Y(-BRg^X+cbE`uT#q|>m7 zc^sIdbt26(0_|WtP?V<-f5>0>{eKVqtJ8jbxHAGY2dhX;-6aWFeZNP(*#lq8$Nv{G z{|globZ-UNttY_86`z__M%)Xu$b(PBn|^~rGy1c&Ed?0;fK0%lmq@`WD7M`V{LO#X z*Bm1D`P?Wjc@A$zU2xu8EJehF?#PTq3mxJ+{@3sH^8)W*>VIft0s@NdHgz~|hf|Ey z%Q%??8y`C3%6mQaPaz6>nV(}sq1+0v=&o_``u`O<2_|7x7U|$O#4YggIY&dg$-i}1 z=c64F<3A1Vtsf2mnRMVxG%31M&Fyadd}OamZ=Odp$JhlvFY4?*tBs~&j6m#-2^O33 zH~n@$_Wm_+|J@fghNA_C0lMfSO0Afd>_m*oyl-E6hq0UAoqgrQ-u^FuPJ2(|Jbs7Z za#_ycD2DofB6MD{7l7oxh#xj<4h9%|OE!c4clNu4fLKO*lMJ0y&Wp9>P1exsP!6-d z$%GM&x_;O8Vt1#ewN|Q$ptw+b;0J7)1^BDw-JROJny*{PrdM)wP|{jQykqjs<RzFN zO;XFSfoY;SdLsZ{efOQ^nil$B<m1ofxyPG}-Q$fB9a*M=TBQs50So}k0UtVjM`HOy zdZ;E$bzL3cR06Hu5()mZ&&!2RZ_9QU3Da1342mx(fuB`6T(@f|w8m#uL+Y+R|I+#o zZaf1Frq(>g!LDkDGzYT}+hgyY=??W9$Nm%sCdM;569C{fF?Po^*Wb13J>Hc1`a9i^ zzVn)Uf_KG7<wod`DQ~*miTO!T%C<A9fh^i(v0!tQ62QtBem0ekKBt!ht*Vo$H9svI zS-I;UJDD8I9zw!O-6)I<BxDLb)FaC^S3;tYnT5V+qTcHMXGc_1DX4Aq!gm-9XH0#N z4i!)Xlhw2nJ?t$G#e?Z2pwpX55$^U-)nfn_-KksO=+iD!+0d1%bYnJPx;VQt5rE7$ z-jd<^Tf=R?X0HF~Kp{XoL*SAcUt*Z~IQb?8+#G5=e`wp!i^b9xYCvtSl`vi5m}aY3 zQVFKa*QOO~<Hs3dj6B<{dHaP|=|m?OO&YElOU+@1#dLq8Mn3W;Mqi9(ut&FQ8Vm`l zIjBDT4u83vG1AI7z@eEnC|28m1_lFwlRoO2W0U4GywdbC9k94_2S9s!MHLwD-@&cS zWIUU9lgLcu)S@S@neLSE_J{xKPmj6zFF!mKO`m`yAWG4da!1NXb!AAUYf*Z!Z1YX{ zo`O!2LCq60JThMqGbSvFMd0O!7JC(2PMa;p#yZog;hJ((rznJH-k>?_pted!Nsx+w z`ApT&*^Tp_$eT}fjgbmqvdOiE4(os|@lY9UzyWaS$)Cek32urW!}H#%D1fnIR0`;H zG6W{-Z(t|0ixlcplUkXhaDIb$0vMV8=Li47&u;a_U-~ebJ^&G@S_2TADDD8|)G`l2 z?syUG8=5Oi`eLeG?<}#TikPReW>uYxWLV0)ZOLW7$ygJUE^6(0IqufBbP6<d+o*Sr zO^QZm1gP(po3DOFvqV5VS2Gf%iPl(R9}(6W5U{R^G@wi9(|%Z@m&wE%X)_T45tv?W zDFL?ps;<&I8#RLlH~g44dBzSij$V1nHOyM(Lk|Xk(!2y@iBFxD#5IN8h?EOMi4}!; zdU<dfshmkMl0IMZyTAC}KlO9Vf&kP4(#3#u@RC6am9CIxB3R}N;q9ffUz-2>?9>?m zYQZ>>d}4++)yYg(w5d);)qgEC8jcVE>|gVriYNSvJpu$LlRYlhG<Aq&HN3lA?9w0h z#9~UK;&~Xf&}dp{I-%1z`%E&7_;gHZz_4;Ps)w{H9W6qDe9;!5OPI!se$Q3MmZ{GV z{(pV8PQRp&0^`M~05iwHIMs6Vi!{5Rr4rU~E9R4w=}rxCH^VFiLkK5-{4ap&76YIK zAJpgp)Nlb7<2<CIlc0|m!8302`oXlBGmFX202sEfCg5D_KRX#!ZyKH~1Xn)J5!E^? zMgRy7)9F-!hKW>(LDb_YkNiOwR_1Ucp<bbjO#oGEpZ{So_{81*V6qcp8+%$(>+pcC zYL6|#(W(ZMAREbnAN1oM|52V}eTT`{FmHbt=s^H2&?~V%`f2#}UJM2xD64UiF>=;{ z4x|D)I#fa<MRWzc*MhmLvq{G6u7MZ9w0tZwsvVU3YBZXwllh>Ub~36MuDB6^pfVYI zR5O!=@%=Uc=cvK(KhQLhzs^#P0PhI^E@Mh0v}zd)s#2*?pLZhBE|W+`UNQ(Gz%nA# zrp;tgV4($x31^9Hn4~;tbq5P4d}3Gm9^t$3-@T7vX73(gRJu%p(dg2TcIdm&DJ`J0 zw0W4;5<O6}l#hVAMr%cYigN+f&0ce`m`|l%7RC$Ng56AZCp;b~_g}Ts>MOOWPDTae zWpA^?bQEs!`%lMCqOlpQuHPd95RdfMtg$Q;gobH2mdZ)~rmE+%*n!#zYUV0gX{V5( zQgu4*Z5MWQf{w%ORe_;XZp?{0h84A>g&L-;qYJ|zV7fF2ffGN<Xm0u??N4FyRc9Q5 zO#Es$xEcoNX?4-|j|X%Bm`gz7+DB1gL6}!v0$fL_ozKOeh89KlLYVf*{=2=>09w2C zaI~pTMw;I_)y<s508!;H>7$?AXkOa6`4WOdooU;=x@FZ{YLwb+bX9O#mdlSN0xc*t zn!q_wE+bG{o5+S+o^XtMG%jaY9V?@!Yg1d<UX;s5s;RpYfdvPE2Fd{fVu{;-#h<o6 z!|Pb9JI>hPv-S3``lH?mOoU-cNN_$pN|f4z<W!46Yk(F4D0nFbp+*7fO0F|rgP!(A zBJ&c4a=o<01+}eC24J}8r7j*P&b6MRX6HhH1qZuvLTKeX`-!jw*l}zn+w$|(!Jaye zQtz-}Rh=HjMoabw5Lzl?iZNt93s|W-Yr}ENC9`9=cEfR^$O#MqRZq>#LCr@32u(s^ zWhgZRZtxki{s+5e>Qx+0@x0^*eYTj>NA|#<*Xp_?vbaK31|nn<9iXEowE`4!5es&9 zp0YZ_9D^ay{){r>g@<{^{Z%nDuTBQwa8EoiE+&a-g3uLkhH%5i<v^ypM&%|+0IoP} z9OEZBmwgkXL86NN9bWK8I}QkGgf}{RYVbzUF_58@1YI28Ot!v00WxM>+7dXTXEuXw zx7!40cM5U?Om;*$odHBW%*-3&obxBX8@^9bcptIkULB&sj=(Vx&UHcn1EA9xgad60 zDgatq@BMhuCBuL(<P7Ir=lRFW33$=ry^Zs}`b@VQEvu8+W%{Z=>|}shw`mMOc*n-F zC5Uh7*r%3djvA(0-`>b#BIZ7y4~waQ4_fc6yX;#@olpxP*D;*SC(yz9Km-xsqpz@t zi_l#YqF?}nfgW&FrdQX#K^0h-^1LZFb=ueS80)=LulORF883!~PVh}DTErNhd@F+i zJs>2YWHmstf~G5uhG$E8`s^G~0-f0E+B|qQdRU6xeJ&9M<o)wN6lM8<2_?|UG+e_b z@8_$C+82X>7rII+6fHVCrRE!2BTd>ap7Gk%0l>A*cS?@aMin-O2{)vhoF-$H)&QW_ z0T|z81TE;I0JIRQu0kj{(o=hyYZ}>u+mv4sh+aVZQ!_)8M3CaRS_SEbmC*!J*Of3> zSXUZ_({O=Knig&KG!oPL{n(E7ktkZQu7K12C$|I8-hOWDt3H@QXYe=>PZa)dbBc_o zjdaAc(iS480BC1nxHH;YAxNwUfR<`6Xr;o@c^&7Xrl#(Pj<bkY2Q$wtmn~a{CW$UC zjfQ1ShzJWn$2ZZPJkD>ao3d09su|Wj79Q{-H)jUfWZkBMU3G&hD$T%FF<}LKxuLl! z0738c^mxwtATQ^zL~k1+tps0Hwh>FodqH2&-fsf+5HO?ysnLpu13M^mDz!m(4I)pz z#Z4<6!!9~}r{WHXJ6a4=C>KIhxlcJvE-^A=g<f2;UL5l=xJF(M&BXxLD!_V~H8-0O z)}OYC*nJuB;O5h22QBnLYa0y#@?so0KxM%uHP!u~7+nrWt(C@zIRIU40}Ie@mn2Aa zc?RwP)7?Jm;k|8fu`AN?*<4ocALAYyPVD130zv91`G$f%^q-qrAl7*2*k-ijn;|Am z$idN~vS4M<72v@$JJXZr4+B}*Zp0Tdg~3*Nx}1d;Cc<=xA}x_G?ME)??N9)EKsPE~ z5}*iJL04v`7HG`U7D$ay_!MT=1tl>orY|^@?e5wSVOHirhe7e5(<pZX!;u`WvmHXH zBtTN;2tr=L2ZD{+oQtJwfvrQOK?P0|Gcq-kZaj^qiKaBC=$N0faDKHTVXD+MAv7pR zzt|{rN`Y1tP_JzVU6W|I>wnz6|Eslkj9u^pK&!5z+|bs82F7WrLM+Q<A+*JHCllRa z^||-QI6_3O7B($bbYrNeAS?l-XvE^MV?~4o%kbKwGAxAe24>x-71$Cyu8x^Wm+k;+ zP%)a*BOmWO<mGyS0l>gW91ZTJ1!+b*fe(>7YgKBg2%eL7)~`JL-Mh~_I2~w!(47SX zrw%|hDZ@z#LusJj>VBs$@%AYZcZC0URh|K|ce<ojXGGYf)Cc&WRS>OwqkJWos{fJ# zBers*DZjpg&pomUiJ7n13d5t#ppaP2j7F-2kJ=osh*Ifi<kCT&6(%F*=vR5w%t)+~ zEgp$BqRqbRJiqpY*T1H2brQh{xaCLvK3^h{MAgw6fs}<Ek8yt}JBUL(uchd!Y`3oX zSN;j4TtR075s2GHS^$uUv&D#{52gkvu19?feb6%&l#O-D3#)0jhG@dLnt}LW>_D!# znJC#^%_2?D@?68|I`q7*j|4DI!BMRP_HKW}_b7Mt**X2vZsgkLm#h2qxQAQH4r|A2 zmj;HLe5RZ53deR&`OLoix4g|Ki8ui>K)I|8r~oa-xYI#(m<rS^2ZkeKzjehEm<%wi zAZr>^>MWBl3^+c1urTmu1OW90l$&d4f<#~hG%UEVm^XO+kBHOeQ5t~_CLy#}pO@Ge zYj#AyMuXTubTvI<b-`2VsRGbBcyIH*@poNb>Af9SDr;x4@=vEHh8A*##LC{(hTX{x zWCpVX&y6+iw0SFFkvqI3;h>?UsY8<ha)4!J(p>;_A$@wT$u>6<-EblAjyeu#>M_Xb zQ&s=b>lzS#S_u~VHo7r%m`6&M_;ZzA`0n*hzP?DYKCd=#5RM;xRwgs9v1DksSV<48 zIO$}U3_$<Z6M>O&6hvs^RIDpzjn{76$akA*S9apkf6A)ITq8Z28I875YbRFMBu=mm zAp;74CUikH7aGcv4+C!+S@y0g*d+r?S=!>l-ZUtEp(b$T_QxX)5kWb8ejX@L0tm~E z6X{CmgC&_Q&PzV|FBUsJIMA5Twg8BTl~0Xch;MFZ0a5+yljF`f-sA*uwE+_7AwCwb z6BA4P`<%H(rvIN}R6?g7C#TJm&*>)DTsJqcX6B@g<IH1?7(j)nX5qOiA8B+6Axein zB3zMk;I_cwwy)`Vi;F|9V@Ib4V5$A(LU4=NN6$zT!VqXKgo8Um%h;dZJ(=mvq+JW% zf)oRD(YJ?1T=EE(kT9`#(@Q^eYVS6xc!wCcOJRDy(G&m6b6^k{dG7=gy<(5>=TY~r z5VQZc*$;|5nIT{B*OD*_1*%f#A_6vttss5K(=vjz6Q<O0ppDm|ZEpV+*ryBtPIPoB z3m`?J4<SGw6(*cJf~g5NZ^h%QSv<v-g1&1)S*OG4#u(>7T8YN7CssU?-0*@BOO#KL zKk$sRH??4gewM$Rg7j+M6fpo?isb)GO@-?5tl>&piMm#ui-3Ss%4DU{>E<|Wap7ac zvAhB3U1xIkAwYRJ7ei39hM-bwxLX7x=W=dl|8NtNMn!>1$KWgAiECXhHAbDhe5RlF zL9e&8()&U6PTri^IsNhurs6PI@HiVOIM~SnLpYm8>G7cpO7nSyV|jpzfaMMrD{PC4 zaktb%P=JV|#y~$FoQ{ZI6DXtz;qA6v-`Mf|YgV)Z4r5e84&a-oYtbMA0}Pi8Kao23 zanZVX6`Cax{@#}yx|0ZXa#mD)wlT-<XfG2*vlig)#_whh3Udkx6e>U|d;-XWH(i1x zi^$&lHaFv<p3Q@ekVqd_i@?YuM(%#R=+92W9=cw-xw!q)>sdoK_&Nk^5!;;vbm*X) zOPu%8?a!Pzn%<G!0pWp4h`J@pRB=5X!B<DvDHzjQ$|a&Vwo5{}50bjpb9fCSL>NX@ z<>OrQ@Sv3a_IlWcoyiPOVlt*f*NF~Im1vkKBhUCeL-L4ltq4%5*@Xj&UZb&im)i2( zlV?;GRg>a2EvCK7(L|_*#F~eeKeFPH;p_4<4MnE}U<ax8O(h35b{HO&is&nb8!5>` z%IkWTbpta5&`K2+6i7ILdQr-NsN_NJVj0&#<oe{yIVpJmGi&iI#z(hFJ(!a041NRQ z2LV0O84|?sLmva(OjkPXr@qt08|vAFC`w?QcOZ<5N`x$=_h#sm-qFeKwrRg`ld&Ss z<t^RD7?kz{C|N2)0S?>B9%*cL)SQ+&jja!8S#DdTT`C?TfF;tgThi$A-L)c-oA5nM z3@{Ds#`?_06A-WktFkc_pGkUNfRC2^GoiLx^Q%0aK!A-%&EK&hi}8<4j;SnjeSu{G z*qS-`9BBbQ$@dX8A=>cQRNst(3O5)%7Zn#J3_ixj{32dTywk8Iv!f>Hd)XU+pW)qD zak*rTUK0RYh<X%ghysbFA|QZ6QyDHlR0h(7r|KTAUJw{HaL6<vN;)b5U1R7<pczIG zyl+%~kY@l8*8j&k5e3InX1Tg&idh42LSzPh`$(2B7tzewuXW$Qscl+REvN&;NiYD) z&}Uu5)L#SZW3u;VGI%1@Yd;q?zsCo8Bj1m0M!tkKTJk0;z80;ShfLH0ScgTJ8l>3L zyyLf+mOa^?qo*SO<RGP%UZ3I*u^PX*hpAc@TfOVuF39^G@;yZCRe)10*r+KbFrY3@ zA<sD<g>=LJq0@fzgBG7RIq6arZ={C`^FaA5=7SeATeE1&!Z3l>u?*WC>31(XHD`3A z&v^b_zx0pFRR#l}q1(1s9P}ZSir3>XSnBuJ$T1^H@Dd(iE|vv{>Dd<51vcbIS%b7- zK2d8<4g{S3h+{~{$7zGbiDyy_R%7^wSq8&PK}!~=_|{m3v3+p>&dSsbTKYH;XsNJ6 zri8hsyU?K;g>e5HvAfm_Yag-{lM-~ix@vYRkud%6?@Clyz!NV-$B+{!vj{9qSjvD6 z;xX*OXdUv19a!P^#2Go51JfFEhJxSt$9MkwG0+|Kj%ocG!W_h%kvqQ{-Z^3T7f1tt zMdf87DIZFHlJ}z8ug8z^ekz6jDHAcK(#Cujuj89YUX^N_((4C!9=wV_2S&USlrjKY zvH}~DnNF`}3IMMq`9mUMGyVog3Z^D8;+UnV_zB)XZG~k}%LQNuX6Avks1!9KmRHCw zL2|RCEX;&h>q)#7kEm%dSrGjSA7vdL&)oC@*oizpg;9b275oVMqGD|>e>Qz~fc`;W z1Zt0J4eRnPxLX+s=zS@Gnu<zbimr-b!Dyu8{;sRax;x|Z3p`|@nikfWAc0ZeZ;+){ zL;{|ho@_?jBIp`B(e$lZ$p)MUPUM3W03uChL{{5jw*fo|w>Fv<;5+Km9yX8%syUlT zA`nS5L4Wh#@v#3)4}=db*e!P_r9=Wu9fwo)Sgfm&nf<Trfi%EVSr40_6dNVGGDI-# zH2*moheeF3{NS|IeldT+*Yic@VH$?Lh@zw3!#n7(1VN}|9DuRkz<{sgzwkEx0D!p( zZ4IB|<)i>W&3E!HiYYAt5Gfi2WWX2L3ZaogR=$tW>!R~(n#nR2U^kXxF+Pl62{*v+ zxQ1_MTof4I%vZ2ha_<?>XBqyP1t~EjwKfJ~O0N$TS&|A?$AjbG)p7tvEdf;1tN0+{ zz;Foe*nfEb?DLXhAB15)!PbDS;*T`@<*)GxWW9_RaZ;CD*GlFJUS<?@p^z=;51wSF zOcbIg(YfqGkp+8Fp-ZxMgy3Y{M8A3_!9vU;F`5`5DUx8_|8?)bOPka{hX*jlepyhc z22(&>2GQipai<<ilIVU>1NdcX7U02jjEVhmK+enOVLBY9U<O1oz;gkP8(9LN>+?8p z|NaF)NuSdI6H6r`#mN!yux2BL?3Y-Ufm6bd@R%3}b^~BX3XgmS0JvYA^rNWnJ5I_n ze1Po%2)y${EW~3!%ai#yar(6&aCzR$`9|Z)JO`|fG+Ig$0M8)-rAG^qr>@C*-YE!M z;A0FUsv$zSooTUG`vN;OSduP@a4FBSBVL&itk@i$)vJWhX5rDTaHWxyp>oL?ymN#n zfb#Cc22}Wt732!hX8AmvL-x-XqPN^%zQViz&nK?^fcyOI6%j?vpjn5efI>Zj@=(!+ zCdHL_5<4>ulQ9>~yVB#a)bLLP0YDIW1w_C;tVtxm?hsmZeg2lsX%8Tj0pzEtov$KJ z04zq!<S!C92kN9WEEtFt1*k42H0ljhoE=w41!8uCZv>!7ffrHWISdPhMd*FFt?0oO zkDo*jxNJ`?@aa^a$BK6T68&Gp#(aeyCp5G=qMD3v>$OT@!2uv>=$S@UrU$C|;rz#P zaP?G_DZ?RfGdRs2sr3F~5+$yaW%FUQKda98*{N(oM5AP$q4f(nudCm>+26sQG=l&T z{U;L6OeUA|MLWtDa>heTVY!fEZq@BTz$Rhb+H%Kl|LfNPj}3|@Wo)21Rw5<t`1EzD zN8xHrMj|ZBOIZ&dyC1@x0pQ7uUWXq>s5Tyx^J*3bcp{luf*0~$91hYb1JJMK4>2v< zp_=<W8<$Nv@CWhNa?x6-s3Ej6+K0W+!mCTtGywSg3SB<K`*;!`0p7$FD<d4br&aw6 z01u3STNpFp+hGG1A-8@*THoPpb);scX;|<k5@7VJ*mps688szSfB}$cE)d>?TSLas zb|q}~`mbV>98>vxnVqT1>MQxIg;t|kQ-)_R3yd3cy|eH-U4#_q*k8_$C02TFCbg6= z#y7G96Wy%Q=NhSC6T9H;fYu0!NEdE+<%9nb@I`50Q=ri}P#{(68;rtBTqBr{wb%^@ zmL)T!25h=~H%h$fyqL9#6;2)L*@P+a0ygA?C7Bf50Iv{{48SKC`5oYZk}3FpD*O?h zG%Zty0MBF_#EP2G%IKTv&?7+;yYgXv0;QE9EwAJsn1RjsR<O@?=sE~L>{ZLqXH%f# z#I%O}@SPdlmcsYXm<vX(g6n)(CHrHc7`5{Kf7hfTfT)!tw8VlRCxSR!+#EQV6)r$% zSl{XyIf7j6SSlh0`9gGCYQ-sL2JP?!F5xTyz~*imoW$A28R|^lKtnjt*^#%N9&{51 znch2cX=cyRTzY4u-Fz6DnjM7A%gzJ;1Ji&cTF8PYt$U|O{!hA84Uo3!K^g*pyqgx& zPdD`<xQ0E8^{MV)@)Mc)EOVlEIrX%~cs=QuguMCs0Kls$amw?N8_{@5_-&rUm-%Db zL#x&K0=BJGGEV@Qkw0M#e3nJ+Jo49xp9LW*OOHQb1zal#6(^$N326uup;^A6`<pm8 z>~|485V99Fev9f$06>jn=b+jq#1{mX;%&T&tq^I?Pl=-7Q2d=(e~x=PF9OWXPvbS3 zn^MjkW`(3-G-Hgh9`(xAinLPYMTGB>GFF9xc(5M3*PCU?Pe({d+_A!K0#-<y_=YVa zom??J7+<-<$lPc+6$VrQTo7=gu{L@f&ZKP!j4(4fbZKUC<wKcWscTMh;>OW)AC+8~ z9!(pBo?l>DvhT4s1wAm0t}VlP+j`7@cfq@w5FhC39!R0I1B8{dEUOa6YanbkYEMtn zq|iBhD@KJ0W?p^PXAORhz-RJYqLrj!Ev`S?p<VJfQ8L%NDe+GB0$#@9?H8rHO2z?r zEx$zZr?EH%Rg6k{F>XRsmM`bGIr<y%m=%%Yf~4h$qkD<aET4)iPW})vOQN=Tm~ST? z2h}ww7iB{~jANk{c`DxEb#e8xJB#MG4?YFprJPr?kK3OT0e4Tu9icp#rT9+DS|K^A zih6Y5^~&9uVKo?0I3WY%j+VTu=d#Rd01V63WD~oP)GX>6%5a^*4~(u&5B!UlCBrbV zLIWp(aC0R>a_Tg@SQtSb$YneSH;I#pRq&0dn4I*Ta=dYDb|iqX6R`PlS@6R$2BK(_ zR_KIz`p|!UIT5r6C5u+|MnN$kmBW}P1=Tup4fDyf4O_5(&t$aIFn>Hqc~8p*fTwX0 z0I?qj0G`B$5Xt~ScLJNC!ol&4D2*dHUu0?f7J#)8DLVfS0Q9m^P{KxFq>^>G7|1Dh z19$`AdME(SHZVTRvL=%PU{eC1LB5{`LPM6L%@HOtU(e<Y0Dhec1}h{VzK>ZgM$Xh@ zS>2|O%q19OiH0NSz-N;x?Vh}+T!6fh%OiCeBM)>}K6}Nf?6_lUcOF><0SS-^@-Pk` zBVsa2W(u2c8NLuEmnLpZY`KX@&&^mJCH_Nv?=5Gp0Q%t!S*Ox!0MNa3V1Y|px$49J zJJ3GD<5_Tex>O2l3ZNP}=%J(MT2=*}Xxw--%HfvVZtbCjLJO)Uphj-QlLlis-n;Of z7r%J$O*dFv;XBwuLGc)QBKH30p&ZX8`NY^QqnDeSylvz53Cjx%l&KYT%R;Kzf|X|& zUM*TlE~R&>@)u@Cqdhkp7nWSvywkbmq684;&?IYV2@G_SUA~0P&VGZLHK1xkj~%o~ zf&;*vRns6|+5_;XMongPiuBP%tbB|vRr;<6Tt0e;zv;XFzwcf9-51|G_p8_Z1K)k= z{fo<!KjD9U*Z+I(n&qiJ`7OTPZ@Y{2x?L5(VBFM+!t;X}!*T53@@0*i?!Ac&;MGs- zi9QlRMzUsy?c|nC@zTt9@DgF!3i{DTdhfB-n>J3OG(e%AUfmOg3tiAxWa+`B%d4I^ z?LQcxwa4}>+Hla!P*1OBN_mnF&6E@Na*!Dv<=;Wo>xMy2rQ6pqf!AY#v4=nUAN=Ou z_3f8^&M&NI*FICnm#=((rOD#htUvyry!qYxz0U<GjgW!e-?^UQTI*G2=dksXC%(Jw z;6?rW0uaE~kTvjRV#hLmo5=-3`Hf8nV_>Men#OmYu(Hw{iis8wDk3ljT@Xg)0^pI3 zLt8kcyY9WV-UI0BU~w=;ni7E9F(@=?ZJ*fQ+P>{&7w{^{EA1GWF^S24rhDJ`HC}o9 z)VDkLzPIy&-q+7N$`88rhQVUapJ=rl!vRObA#k#B11w2$<UakT;cXK)i7j{rq5%p} z3dRFc3=+`uP=o-gz63y+1NU9-o4XeHFJB0R-(tU6|K?ZpjpN__IUn|CcmRNz8HTXz z;Ov6At_Sn}u+4RZp&n5U%-*z1wW8}W3Sp)%Tpf4*?6zNez#UVk%fO-DWN}$#VsLqF zm*d4d|HbS$q9sh2@rr!Ro?C&F-7UexSnn?+uzvW$5`=(MO=gf<Mw@+Th^B7=P*Fu$ z4++HmmzxZ_MaR+=DHuQam;dG8e&zl<^3z}BB>|vLEFK<)$2&8#hez@R^2@Z%1thXT zUW(UK&c3OT?ur9k_}HUAD087|s2tthcauK7cIlns^`bUcmoJzN3&z30>D(RY4a3K% zwd{*tmWvTeAwcsQ#e&X}As7LDu5OB1H)J&HOA4qyXl1-ZEiB_Bkr|BFUiE!GtB;R= z+t2o>Kg0d3PLl&Xpp_`_2pu)q;^NbYtj@_C2VR5W#54eCsZY4Ixc;$+-VxAl!t~8o z{S$*s=x5@=<c<K%4904Xd*X-viJ{!>@EjZMx^KL;ljEHi*8o5rk*ZP28`uVt!x#2W z%ve$t&@Dq9b;j1_=<vaiK1DYXW(K0Y?}}&f?AFZh;TL^TRyPUTfyWL2hNt9yxTIuT zTr9NUc#L)*6Rw*qf*!2V3R)jj?Vk9|^7)VV=m#R_W>7>N4np6E^<FtsTz2hoA9M8D z#F1mK-m-DaN*9$O_dt1eW2ylN!(vG7-8e`cBYeo#=IZeofPPbipm^`KEen_y-C~h& zdgsX8*m27*_dNffFDZWMm-*=)U<UB{Va|lStWYP9cgYwiY;iF#Mu!+<<aGyt*3w1s zh|;K2p4r-R?$HlU-rs~1^ouxYVHrM)HMbu+ZsEq=ThH27v(JI6$HW8B>)(gx0033# znWDCkN4<E8fUr((yYaMIL2HvRFhsv1HdM3?Ev?_`5&AR|RHD77d|B_sm(PB&H|Ljp zQG8{vh{RAv1G2EeFW%(XMr?D3kx*Z)3_jkp0O3FY9JQr}fGK_K!H%o4Ex+r*b|uj; zoEWn~;p19W$aZyqwhcUl0fYk8<BLdkfwSyZJ~fWEg{v(v7=-mORQ$W1X9+sM^u(J@ zPkzey!Ee-i%`fvNzrZtq1-R{?KA7*A2S7lck1}|pEiRS<@?z-zD4XmSkkqXg)>Rs` z4-T{~GL%&&^w9xB_>Kr^_G*2;>OEwA6Bukl7a-ukH3D#0En&cA!PeB|=%us!OG*(U zsVG%?#s{$HFrsG#J8m4k;3Q)e*R3rMex=v>*&i3~CLE(M8$PbJlV~z45AI!+Y;`U9 zw2a923%XGRL8I0-wj~}oN{JSrOc;jBH^lsYPzT;ah1?$UNc4|jzagO{tGzfuO_1@O zZ?=C!W2~_}7n&ffmltXaYz)lMw_3HpkUku{ad<}LGs8<`M~`3o)!&+5;7J_@!vLmc zPVrdh{Irsq4cPV8;#*vtBz@LhC`+**mpyb{&0boC02WjS)d^6et>;4sLlrU~9(a#t zpBuuPnJ#=Vs3Q7=6`_kQ$bgY+6MN@JubkCx=NOa=<*OT|tzFC7U6!8n3G>S?Eu}|? z*RH+w=AAM><Nf4E)@;Te><ODvUKT-O+NX!0qu=Iw3QXBt&OQw8kMe@8Wq`7T=3${^ zUDl<tLeEo8-zkBb59QTd<Zlg+F~-zGnI@E7E5N1QMJj$Pb6CFJ(3;p0Z11hegunu& z%A{&ErUx=3^Plpn*Y1BaXYY%f{~P9E9o59$ZP-0Ro%um?JnkQSP+{8Q;;A@x79#33 zeGYx#5qb@vJf;9Bi>BzbB%;|1OVF6Vnh*utmh>)E4@ZWeL@)e(<OTKoKw`V$xhs}k zV{Hu(03w4N@9KE#1$j4H43k^etT_1M^tE8-ZhiWI&DJ0j1vFjmrukWf8ikKy%j=k_ zT1L>dAHGWHveb#l1fV2psaEot1-b|n6#MLlYozu)Q1b`mN_xYP^VMk#YRZIBNY)~} zp}6bZ<iW%SuQJ{lZ!AwhXaW^h-LRG|<chUeFSQnRjxD>1vkCyPp`HsE@Ocq=#sV}w zQYRM<?6$iF2`&MEEQJCxKt?rm9bH5seWYZ=gf;q&qUXU^iEuxvQAcoVca;+uXQ2y7 zvxi8ET3*aIE?qrtU3=LLH!Tj-hE^kB7FepZ8ji>m+|1Cz=-jaHn9_;d@ysMp$cALN zyB{H7Ql85r!+o&J)hPWI7faw807rDZ9UO!Mi$SyIGn6(MsJhZxwj1?O8fZV*VOMyD zlJ!On6(ui)ts$HmO#`kjCrCm%ssZmC>r5PsNSFblA!(`5ktdR`A9as4t^(%z{n2kq zuJb2T(+pDqe7-AjEVR=$H?Xj%7Q)E1$PAbafRd8H&Zk7^hL3~*Rr$J$CTV_<WPfcC z)_W?XbtfzWcnm9rq(Ey11BQ9qZ#^`6J;h~~>_Y9qYquC2b&4uH@}$RO<T@TOCs#9^ zvUZ4QpIn$$h;4H-v&;Ypbw>iEZd30STA^u~&_}xUG*t(!Rr!bswV=^%h)_zB;C8Sy zhPD%q*fh*BU<9+^@`wMQ+I{6Sc4esV3#n|iHkpB~)>EZQqco?58|pc?h;9b+MgS(8 zCNQS7^?mzSIOu#~kdro?vMTGK0o5F|b<<}pB)To@4OJ}dq^#;fHBNyks?I|SaRiE( z^hF;(^W|}ddC(yl(1EP>2%zIeS5TS1T7_IJm*@)_X9DmxJw6(PscdmE!8iy2kFIk{ zp2eCqY9-w)?WE{4GL?y$>9uS%+j`S{r}hjlh`Gd4QFC};*~p5?1)4P3b0f-nurCZd zN3QO|O{lwnUv~bS8Qx~bch{XF@{=Bj^4e{ER}q#(m+)fz>qP)oJeZUB_6b0>D07{F zYpXj?Fot>`{G`=&L(r{E%SW_U=<(T&k2f*rhf^C~C)c?`#LRE`3a9g|h!Kc^8|=Xs zZ~hK<6=$jmyT(GTc_P%Gy#Y{mt<Mi=!11Ijzx(+W_$Ci;k=}ccgT1GQS$G@n`cOiJ z;+*-|*c3Qfn#S^Yjxm6+h|z(@_%?SH$%7@r?6#dJ1OiG~OIB6og!lRX#!u&)hBKsc z0y7vq3nwW0%z_qcqg#gI^x~$YCV3xu-{7usr?Y9a5Md`c&s~et9(zv85n&OI72ipo zc=|{m-d*2zYzw!QT#uY$gRoLpeQ5UQx?*rsvS)naHcb$b38tQ8$8MY34@}2^=qSVB zXtUCkD!9fWz4h?B&OCX1!z1Hns@=E#Jz~gc)IaqN8<z%B7cE0yk?rVkahg3A-5otB z+AL0j^V^Nn*c2P2paCl{6sTk5ckAB#dY^aB_?!Vh?v}O?QK_q@Z^0aa*z{+k>xX*v zM$QIvKda7oERmh<Zsf)Ysd&e0;s^o>IiP4&8*SvqQ>VVob~CZQ_GNsBhe%A#2*-)G zJT9U}LDq-{Id>?kzH_bv7Hp{_Tgb!E89@x%@N9G;)>;N?6j2osr7s$NC*PWSWAwu! z98^T5<r^g_C}j7J4#!QCc6GDD0jl&WOr97l+OK`QpMT@00eB8C<t1oC^pp8x=0o+5 zFFohxnY<8S|2o$N^{ZE27J>V(1K_z8CY7guFVE*?h}oK;FJn4FcEhMh#w_`3f_6Z` z=6rzf;a`}A=duWSU&nj+cC0GA#&|7+@wCXA0Xhyeq+{(ycV&a=a=;py$|K&q?U!8n zk%qEO$IPRV&IF7p&m6H#>59MzP(n<?(R%60fV3LYp`Otl=jDl|is$mhC=4tDvr+~s z!*ZL5kz4vxuSmkW5EO<dZtrbutS2wAbMxdiv~9*8dAtSlC|1XizUR2<$`Q{Y<nQ@7 z>G=YH)Y5BVjspys$KMfe%2U}DKw=K@SIo;aJgSchHAo3o6>b1xzlTP*CPYlf!RHwO zlqpNil2$bT#ADv+y!&&VbT=qOVWgHfU0o-xg0hjycwyPrSa$W=w!xwGoJ1}=Gd6V~ zm&xokotc5mewnd}o24hGop9WM3V{i5rpOp~l5xPS8!@gOi^dx>;If>R7BfLOe(<}6 zE5%j+aeKot*S;OOn8yUgQ};ato<=%gAAW*&VXu9i>rbI60F})tD|4#I{pL6FY93Q$ ze7@C%5m8PebyYKiBuGZE3JZz=H97*MVrp&OBd6bIfk-oxh_~{p<2WmWOwvYX;K4+{ zoGptIE4e~yH8b*r+|KENOg260n%SXc6+6&d6tmpp=sIw{*s(SOYQRd<Wn*G{Z)>zg z1Y+P5So#Fd`A(fv2%Aw}Csi`hD9`4hc*NS2i2N!c?{Bgc-_9Gz0Q@Q!{XPrwZjh_+ z61HJp#9EK%wQS4=nAEvWW{zHfLIZ*oe3V~i1p>5rHysEoZU<4d>2vcMrU7>2Bdm#W z0S0W1NmcAS!6ta~Ed=d`NiuZlJ{QLoft{I_zu=Xm<A@yrU`wuDh~OV1)}$bQo=(Xt zB4KX^*@yogO8$vVNc9`=n3*X62yTMQptYHvqncVBs3}On>5?lYb<z=L;Q-Bmp+Ge~ zCPhnW-N^OMc%ReHL>n$jiD_VinCTd)UdFZCd0IkkEyP+c^o?e$**+RWzGEd`=Cbb{ zE++@3#Vn@aX+eT}QQJ`}+t_LJ4{uc-*<;8+K(6q9Ca{CiYq!YW*aU!X4Hc<^hI4rW zcrYdABeeEPL}E7rU{Ao_dr5wQCt=JBH-45^5y|rWINwWZRz-v0l*~#G<vp>@c`vuj z#uEWpi67<9c^V@3+*6}6R9D=tL{x413M|N4cqab<&)@^>%eI6nt74Y{zXhLQeI^8; z(erpZKTE*;d=A*1Kjn^tw&yKGqpYv}q(=8i_#GzVhj}iSd<NwExaO_AnPC9%{VdA| z=`}NM0K}DpMweVd=NIroeulrsW?hcAGSD}4vZgFh&LK)!yH!My2HHtec}&(-6Q3ZO zQ!9^s)rC)FENe4nWwObhM|ThPCK5v{I|>#Z_{>JW*EFqUc-y5PeD!8Eo7`FRycw28 zpg^5QT=#Y~CIaqp^0%vdgSFAPI7}x2GC%}Iyh7+?Rvf%l5zqpVH}nKkjk;p$1=Efu zGf^Ikn(<0xUJ3R5ZaN-agzu!(j%)!uhbvl)azF1wwBV%N8Vsv+2GHM|r*Ge-nHXS* z-T9@3?!Ur|Q6i|gU4^LH^aVKo@}kiv#i^@eFIpm^2x*ytIDkRNy@F@)HcDLqyopZp z(|%3H0_3@%(>&}i2anaP-ad8+8K`t=Y^OU_^&4)$6Ihrc2l%B1@aoS#{2683S&^>y z8L(TOcVJ~8DvcEz7SJ@!9ZLXeK$X8tpd!{ynNY~KR=Q<MLM>ENcb(qUO1g4cEZDMn z?uS`XIyt;HmG#|d&>OC-O5A?td(8WR3wNGPz)&tD*8`sM&rlWc=vJ{xUXLDrsCqQY zm{t(M4jaNIyF)Q~FgIwbR_(av%fc>zg9JFFiQ@Ea5xvoq*ryZ)UIElpUJMxca};R7 z-oPZpfs4zxqPco>l<#jP^q{x#PX3ZW;NGQKhVMp$J7`mJJAk;_^v#%yIPEHFH?Gbs z0M%C`8GvbEPXOdPJh~XKM=kelWJXh!?X5mY{q?!gVR=*)Qs=xil=`)w1aO>{;;JfD zYhSOZ*x%+l(Mfr}ij@g30;LF`C@)AK*%F^A1rt}-J~kbtB0ufs?4?(}GoBwd`_k>X z!`7m#*Qs+N<pN@efHNgL8vNBYSO(r-UI=|3CbgjnF+wj9FgKA}Gld0e=HPcq9GpGb z6hI|tZwaF{uhMgPOMzTCIIog@kws{AQAxq$;pnwk_S~2%0KAe-DD?Zx%@)98p39L} z)1wnwRoo6Bt~Nb0o}#MQK}4V)u%4Ju@zaI<MvTn$8wdut7FD)C3Hw~_1{MLx_g4NG zSD1vwxf2M(N)b;(CIawMJoy#SEg1-d=@u%G3qZAnR?UU-0-d(JRp0fyuiq_&1`4cl znUG^7<7k^HzMgToj;64c=G~30h6QUJ%_hAxub3;kR;o8Xs_h`wv&eU~y)8h$Gy+?% zfJuB1KyNel=r|Ex?3Kdjc_s@|a8lHKBVSK{jYQ{vF<n#@urI;QQ&Ssy-+1oDze&6n zH-Vyx+XV=!O?M{^s}LP0tcu-0Gyrk!e1ZOc)&g0joT~#Mtg>CSzyc7DKR{%k%R2!x za^3`_#%l4$8$m2pFBi->vEV1=<R~`qlC~)6g4rD^nNTYqDW#PHDEmy+DVoxRN)Z@c zAVr2ClK|LGYRl>UgM1jk!v@8JX4mQ@JEPb8NIz+Ju51O~i*suTKtP%svxz3#$)@78 zKF!npqxbWS;FZF()b7{q6g5wz&S`fQTpPjlY((@wuo8Q6h8KXo?P_eor`U`%0F(0T zEX>bP+)E>qF)*LH;&vsqx^?T;Tv2WM8gyO+S_J<jE%v2R8Ff|cuKYQz_W<CyU*d`l z`53>z8_`@}dxH^xu*&u|S(jS?kOi*2_cQE5rKK6YE5P*+EYDWJ(^!zHcpI-_BDQ1} zro+lWM@4BF7PVLc)D@yE1_d^}fw(hKlWtZXFb%^{r|$srj(O#_l0hSY^7B3d=L*oT z5Cz6Y<T?rsJ>T+}RS1-)HW&X5{{O%GY%eV#1$qPk43gVtEYQtemDtX#>nyrLmvJVa zUMZ9~{I`i%h^0VXBYN?EPLWruSO!K{pPxnX0U#vd59m<kr|1NbhWss-qkzUM+KeM6 z<K(cfaQ%8Xv^&Z{^``OQ?sd?wNXfk3M@<TBLH_kA`#fIC?wnOEU~$^6$~3SBM~}}v zo$ddY2sWfr|LT9q^q8i;c7WYDx!M#gNu8vEkcEv{i(<p>UI6<E5L^*a6*6=Gr%0^+ za22b>DZ%H`DhZtf%~iw-e3c5-C{b$GgI0CsT$)Ne0Pui20$3e>_J=%<tIuEun<3G{ z#7jRfKhh11BDFmcxZH4|@8-h$_?F0ra{_6jAc{fj6P*f0&;%1e3n9QvuK?~*;tHV; z3#vc|zK$26U6r1}Hn21zy~k`+?iMH}!^Y$+Hh`$U_Vxhp11neLBvmAo5uyx0fNx4h zSxBlhXVZ*l8t!0cPm|R(Y-IS&ck-1xV7uZ1(7VOvkH7cD7LCcRFE(}X?H~|(fx_NA zoQ!m|a|{wWN^0Z@LKEyPUv|@omlaZO#unE~bbYJTiBl`&=#;Pil^xRQOu;kQqlw)v zJ2*|D#|`{`+Pm#dlj`~GYfg4;a*NK7c=CrmaGOo@*8DWNXNWm@7s|SNEL2z(7fd!( zufybY1{u6a3WpB1x^o2N7^96KqaZ}lX80U*b9S+kvSC+rO^{6PK0EMjcT(Gowju|d z0<QwO65OfrgP#0RGe5-qL3qB4%@Fvjm!f72i&Zogki$riS2mHd@U8Ck8wyZpbh#A5 z(d4cTv^o`-y{j`9!y_EH1JGjxckuXVV8A!sVJ}T~7%T@s9q_HNr{?`C=(Xyxz}Zs) zL4=qBKTj=m7Teq*1gT@_u@IswSLr(+3yM=d6?}vz4Y4-emH7!^hvC4t+!2fc5@eQM zOE&7fY2Fg`Z!es>97iGy9Fu|JkyF3jO@f>;2+<X9g|0ky!zr8{BhBO>J2^K+(UbYI zc03I8&VX;Z!xr06eV77-K&cYyepRnG7zG$553NPa-a>~01Vk{ZH<2T@x(m7~fCx~Q zLM{!P;HQGb)K_D69x#ftrn}5w+~E
K^*?~X%m&kd7Va!pAM$uDAKklZ2Al?8>O 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 TPgX(X*Q9b(gjPZnAPq<W^y4NL literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/02_vagrant_box.jpg b/public/develop/images/deployment_guide/02_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7485c4a8a6e64f3c4861d0a5c811bf4b0aa78b5 GIT binary patch literal 21135 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 literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/03_vagrant_box.jpg b/public/develop/images/deployment_guide/03_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..86075951114ac087a162fedf207e77290adb72c4 GIT binary patch literal 25920 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 P%b!dQEv{AR-+l0Z%<YpX literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/04_vagrant_box.jpg b/public/develop/images/deployment_guide/04_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d427163b1674ba9de302c483186279743f94e66c GIT binary patch literal 28683 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¥XV;OF#sFg#+m-g%T+sCn zb!rAfzX+xbn3MnNua;sEV!&zUSQ&60t@BDlNOcH%zEtIgW!FU`(S*~EKR=83%)Gz1 zN$dOl?pwej-kghGnoKi`H<ePq-gFmYmQGGiTk@&7nUCLkl2BxsA_kag2pRztm2vXI z#o&oJA19?MF)a_u$SHFX4OBV+23h0JwJibd3t6GsSKaEL`8tK+FAzf*4R+1WcK~R! zCF2-f;{2cKta)8u)-0eWm%P18$HM#`vltXCKvDLR`?~uI-G5pc!(K-Nl5+~+dO!gE zc}pa}@Ex%6uj!|4&OF?y#5pWa{dRx9W!GW%u(KF0nX)(*TAIS{)Eqwso@oCfz>uMJ z5_@4V8-TQR7rV|pX?9M*n6o!p9RksFvZKKg$~XWv9}K*sLqs>#N&+bIlBPwII|DK5 z!u(hJOosq#6fpo71>x|DBTg(h@Z;+<^Lf9Lp^1dXZMZS~5oDJDXm}D6g+Mk7G=U7X z+NJdoE{gg+1Qv12@WsaWcRfjs;(>d^f|h^&buRlN6!8Hn>I24x*UfeYzSXGvXcNd= zXI^ZC{dF0K3ZHcLhQYc-gV+!)y>=k-0K^~9|78rZ%m3zMyjZ7+37q~1bVTGpUtiyN zgP1`ZNG=YsP0+UYb3>J(^(5~)dm|&G)*YLmZFZ^#ZJedq?Z0WN(*7JTa*aiGb@lbc zEzs9`JFOwwb+^7nN*?-bWt}3C3j)*i#Py>ksaG|{AD_LdxcX`Tg@V_wKQJk&>Yjj@ z+BpSN6RuR<d?`swut!`3iEEfl<`v(w7#b*Xh+}IjDk^e5$>@DQjRmv-9B2?NtlYuP zSmR*Vdf>cXxLk^PNJnRM=IVqD6y<@1P{u2pqlJsXuE5?-=GE5%wA$a*m6f1ipLmi2 z-0mib%Z<57Tyl!KXJX^8XFk)-!bn^B1g2CB(0r89zoJ8?Uq1>nQ5K*DmS5?dubWjG z4O?h`-V2zI!s7c)Ldy<1FxZ}0l!0kQRJQx8;Pah&TrV8P!omW;sm5LJK=z5K30v{i zUH%p&rSNFG_g^MzZeP2=q$~QU^IG8ii%Cv_DIf}?%!4g{eK{60?U%_-XVCI6+PKlc zG{Bfo2F+g$rz#*X0;E4x1xQqEt0YeV25Wn+Ec`nC&ga8V&WGyNH~LT-Lx3CezPz~! z=XA&Tk3ZA6_37`uqhrcTdr4`iIungie;bp9D&HOx8QNF1&OX5Zb1FNu6c+dZ7cT;~ zMB%V-1jW{Lrd{0}rDKcm)ht1!clzSo!506?6!#e>QJEbd2<Q^fU1p6`fN7b^?{|2Y zF+;r{=V1l0cn!jeg+->GQzmcDz54cdLkd$IOv7YH26^18O`B1lP2sa}sTw{A9D8lK z1%7L+K3I!8SGn}~LYN_&f*}CmD0!tGk`r{8f9ow3UH~xLgTo;a%Dl-nu6meX&B9AV zooGAv^g9pd-;M9~9;-jSFmwUUj6+5770TWj3h6GsfWELuJ7obqZcL?!L6rqy1?i{# z0L>Jd1@S9|ouufP2T=blaPmZ|g>+oVkH<35Ah&~1u`tz10^GB|yZbdr5&oXzAO=lC zqxDe;o*t~+pmf}+uFp^OZ3DASq3+jbktOJ?0Q(NqPQ5A?2TwJcSLt-8`C!Omx6#rP z7UR^Le}SZk8+qeOOaxB=T`8D3fuf-qp_E?xM&?12p2#hNCw<0rblD?*DV3rIhk@QQ z*`AgkI$tZZfM~3t066sd>EUd^21*={1X03qC_Md?iEh6B!7GonwY2;h4eXh5@wInl zbu;ojFj#g#sXNXtM)FL7a#byDk(x7GhPT8vu&!LAUz0*=Puw6efLmWpk&sF-Jt$gm z98v#{Ob8kaZt3;~LSSER5~2u7wq^Kv2v!Gt*dly$g-Jb^aLgWz<dh!!H1FplcpsNR z$P8YCuPwj>$lkamP5fJ5swsqrIUPZb*_|Mab7Iz(g?K-N317naK5Hx=PP-2}yEAN3 ztq!P_0gxn8ko~+ZjL(_Ht`8xO!1D%x$8v48bM5Nw;SL^~g6+KkcLK5y8}Q?t(|(gT zfnawJ*M52`JzGd`Ss$oEt@Z34Gi91M|GT(97;{IZ%4;7sLe5q<nd-UNk40YshxFna zmlXR424+${%esNqG*~KXS8}<Veth_?D-qyV<7cAU1AZ%hy!jwPFNm@Mv!%G1L`PKj zu^62<kTs7sF3^22j{Bv}yM)j`HvywC7ruJ?9w3c$SsvsiK+)>0^5|UzrZ5Be-(th= zAxpg#mj)h-Jlqh9%UShC0(Ay*!wX=u+YpqTUmA(EhL&Dx4A4w5hkOBKJ+?p4MrWbB zgv3oF@o?MNMQA1=vuxL(lz>4;CSCP?3=(~KvETeDmtQ}cff?K27!#h4410&_7qvkS zy5Lrql;pX`SKJjtuO?}wx-ruztLKlMf7<1Z8_fes5KshYbR1=%vQw`ZC_)D=C0>n_ zw&+<RA+bPBPU3vlXnYMU%^qlj*y@|zM<LrBZRD<WVj9V_kT#s+{;FHJ@#mJ(tF_kP zyO^<%S;bxAdRJprdvgQl2LCeQFp~%+(B#EJ+ASFJ%W0>)d+xM|x;xZdz;Jr&fd5FD zK;zkoW~nJU)!70}c(0}%$U^?#9|+F?BkTbeNlBSm`Y6utVsfGEn-Mft_(&Xm8TeF^ zq`HQNCufmX2S!-^#^;?*=oKo5jadr<YM_26e69Y<l`AJP{4np4cNYNzC3#{S@`|yI z0M{%rZCPP_Dw2N=K-}2po9_Zk$^v_<-eV^<E=oCt(yL>0zzBIz+Y;8@7#LCiDLRz1 zr@LOyka1X`F$f#4ROW75qtodCWp5#)0hX)-9#iE?JOtg?&|6nXEY)Maj9E0sb!pXu zL0mU3Ae}<Wp@~t)tJT#hIH{V1C<fdbibbiX{qg|R=+GSBiJVQx?=r#96ctMjVWYsv zBoaeuK_JhFA<ImLfX+`<mkUf(MwS6E-q^Bt2(#LPS<JqCIFmav<<A;&p9L;O+9aft z6}&K62!2}F5L$B247bCqj*C|VwhJas)z9s9b@e5{o;!BVm$+m;)|yPd0W>+o@lV}G z;sE)5cFDtT9zA7`2+nO>Y4@3_A*0r%U%=Ff_e%X;I0GP3D#mm$$hZsn>Ap_@BFKko z0OKt&BoHo3Ku9t2ssHs5fax4S{-+<yyy}WUFCIg-q1*u=g3KKtHPudjD*}EDJUB?4 z4L_R%rQ~890W+`$OcbDzoPmLXNN*spN?N3-o&yqHpKFp+9)PG9>S%dsK=LGwa~}+R z?=QB8f;&k2<~TMN+z8-fL?yhr1|_Vr#)ncCtyE_WM6Xv=pMuMF<QMq)Ix~QTV_gWl zCv7Zwy8&j2-);?L^7Ir8LVnZMon12Ul~%D{_qX|0E7yN~dRzyh)Dl#pz}7QO$2_%B z1YRJBNz;>QJ_A_>QHvl&xa94s#^La{GM)1}T!jiEQE9Z^d`}P<fGAmr2%$-h-u}1? zG9PLXU|LcpKr>Ia)cN%Zl?g#}V3$4Wq^sG0-#P*QTDRC^1}ThCas#5w)XBiP%OD9_ z$Yh4SRd&UV=g_l(T>CXzG8<K0?=_^_o~mxv0F)VTF3_=S{ibFh4+ZGr4zF`<^+HS` z$^#5iV>lP$hapS-_V^6QaKTG!wxqHO<Z6glbT4$mvC82zGY;(nrpQdG_EY{9VK>rl zODYS5l@m77#y}=j2!5f8UcIZ!Umgc)-K8^U&SVAWcIJgGq){NbI3%nwl#S9|{rM3R z6;*o+-^QF52cq2-d|>G*ufZ3m&GzMFk==fN`%nkK)FPh3vjh<_2hCr|oZ`&`f~O9` zF{Blws@!-;4eEfeg^l`k>JC?XCMnfTA2l(`+gDvxbraZ6s+zmym_)&Ww?4NeOdv#m z(zWe?iP6?+h;smZwXpl{v6sd;kzJczfhR*bX9>)bvF`v20$}yQ3;qC46gM^^_RCmS zts-6r+=Wp3dWmDCu-8(%-FF2H*ew7(0Xj{D04;L$hp?2Ob$V4)f+1uxUlX=lbJz?9 z_wduJG=nt}pZeE88=QR;t<-k)6Pl=qD*$H)^tWrGH7)>PGj1yT)8qY1O@hi37C7*g zO`dr-$o}!>($(&rVmtteI>PubW5Ui(y$UUZaLj1PY$+Jzeh@VRXt_z8D;-tVRqX(w zmVj_(2Sgs)ouFbbBhi5P`T7M*Xm3bh$}>-S!(s)l52Bj;b4l7_?*&rR13>`En!avX zX%WZNcj&UOhO_aMevsgPn59H7MTxIMj9-fyZ?kR<0J`)O__G94t+%M1z&xQKuhh8g z>^)j{i2$U{0AQ1DiEwa}ivSnSu1sZ;A~tMd?w%OEQh&DsgfZ6qXj-k?#5VY{K^{E4 ziU$SuR;i?Ez#{uVI*Cj9kyzZNr31_wy6yFJfeoj7LNN#uHzD7etLz+rf*6Mr0;D-> z;1z)UzqZs}vPBNR-!4Nxar>Ko!$OC42zxY0P2;9)-#V-rMH&3rcdp$7%xYf}VbwL9 zH;W|wXpV-;6h>mfXQAj6)VLrz>Pu7vT;t+3p-gS?)F-dKhY>r0>@tsM=m82i8c6o$ zK&gk$i9s}k(D+;iBtZJNhz3ow$UY>H1At<F*x13W`xJovc~I~JemBhGNhMu<vrRnA zN*QQZ0Q(jL_WEcP)u{t65Duyr1Hn@n`8Q&8?Rs7Y$cNvEu(3SdjPn(~(UE>S1SEfT z00q5=-kyVC!er_bkacG537iBV6utpO9;$<NgEUR?Uh(TVZA1Y%B>;rAnMUrs!d=n( zB9-o)z+Nl;DdB7nz-&S|wEWNsZxSnZvGoGlI<{_$UG3b!cb~9v5zd@Dym*V?q3MSr zs4h?|N=?zyaQU*QVfjaN9T0MsN`1l={1Qvz=Z3#WBUj%5MNBk&jZ5jQM;1^vz#?jo zdXma4fFKWEP8i-2G*o*6Q(#C4{MLb!ETwdYNzc8>x<3u_R@+BFBA2?(2+19acIDR+ zq}EHFoBx(zl{HWNs}}&Gju98bf-QGkdkbt-XYQ!PW(dwuqk${}ex3qm4F0^A)y)e9 zBP8|I7up&j87)CAsqEO1{U`LXsB61Sde=DE1){zub^taGezHGlpv#?FT?`+ewSKfa z=%JGswDr<N*LO?r;kq*wmu<UmnuFgj(L7ErkO56SKnW01UIwX~!H!l^z?zFevFF^; zmtRAHCIGhmM^+8dXa|)_%pX+f)K(83^4)xZ*d6!#mN)<JPw+qcxGyqq%}FG1@6RBM zHT-}LrYi}^1@U7TyB>7;zFFH20qc#i4R7pc+7R<gw>XsQ;lO@lZ~QHn!E+T8)#3Dk z22+H_2G|EbyioP<)PYLz9k<q8naNp4_U3Gs2>qGHZ@E^kno_S)sgTy{x(y~F&O#<& zJqAxSe^ffFPTYOf7jwJTz@iYYEEzhqNEssLM^yotmyx^1vc_n*9(N1Y2%aeRtXQh_ zMB=F%V6ygz7MVaUGX7C-M{T@BuI-Lp!+uC@(TQzKwY_!k9Hf(yICu-PJa3Mj`{R`J zgN_%$g_%y=5N~l9%zUDSU)seNrhad$^{ZxEVIt$p<2l=I3=5t7%3R*-a=QK4-;A_@ zBp6Kd;NoHs>m}9u)0e$<KiY_kYrHA(?bh-aS$p|maJydYOO=K&geFZ~J7Gi*7SZOa z5uU<x48TtgpDubV*@smsyepru1Fs{XKi`)(@+^{Obi`aa(>{=AW8B#L9hBqorj)f~ z1TgnbA++4#^5E(j&e?Z(`@;L`fA1WhcdlJgiRg&I6M(fDyJz~<9F;4yx42r-u{$!} z)OJp{g%>Y&SGaDO{LJ*oC$3YH=`9f$?Cqt6yRV1MQ*_P&Jx;y*Z_dE^T4^Z(JEPB| zh(DZeu4KWL^V02z!lsp7iW{R)aQNNYGs(S7swc9zM&V?>2F3eLv?3e^Gr0X0Q$@zy z5&Xea4_UmKE17F;ZDDctC5OZLdQJ{1FijF;wpZLOunok01Kvgb_(}i$>~hm5txM6b z2}4=J$dHS>Hf_Dh9#aefc;R$h<h7sW2B7Q0UTU7CP+>{Usi}zpbtMqZkH#MTbyvqv zTI!|obd*AjV8va?%8OcGv<%SVZSrLIJ(ho|Q&N-R{p3Q1)+O**;6+SQ7Q#PW-_JNa zydi|GH}l0I{>#OpVtW2vpe0EggDUOyBp|uL^bdl!Gt<+{p)dBmltdZq1l!&p|F6A7 zS8Vcbk6>t%ENGAbga5;P-P1qr$$>upo8$k_KAH*u(YZ;<Fe|bQdAb@2O0;*Vimt)t z#dwdKz&uNU$4KgGs^57rlVn<&gakYHfok#R&!4-PSQo^sJDa@7A(t+)fEO8WmfP?} zEIn^c<(h%|4h4O`rZTTwS$r#f6HGt($93VXRM%}vwNVKpS$Tb#!m$lr9G_xpLtm%& zY4cm+GCOiJcl!$VcX|v<FUDLN4g}66E)zWAI3|Q3PAyi3(Lx=7A;@mqEGHG~ljXLg z#jdo*%vUKrNc?3Dmud2X%eWGG+DM0tz7}u3^x}oOKUX>=LRu+hXmkQx97{DLlJpF) z;F01tMaI8~57EEqu`;xaziN&htg7Gn=SMGswXF2F?egBZCX}{K8<?pV0@m_Yti8jA zMm--7Op3m5dmZkZ5Mp)2jhC?)BRYR#8%#g=m~o-ikkLXVatbY$D6!C~;q+M^V?&nl zx=x~$VRqc<Swn2j0NrEX<`Gi+2P7WiqYs|vCO_kEfMsdz&9TjL-s*o{XyfQ4W0G4- zQEXvtca)oD1R?shmWZ={pfN~be{aLaYXK7^`UAp)L?O<Ca~sW__rxUIiZ_QKD<aKT zM&d^qi=|AtfHlpAUD%}6h5k+xjCSoN$HvZ#9ArTlsuy=f3>IN3oTBP#`nwIxV`-+p zsIYK5-W)QZqO<Pq`RAV<$pviLSn}v&+Im0IPDpTtQqX^Gn!kWN>4NYN4D=OrhE68c zJ2W&zKYabA@!6Mv&e*D^*dBe^^-N=|;8L;OgUe^3efnJ%r@OC|U%Lk&$eZrzmurul zWTEX}&i`Ls^1r^J|Maa?*T8^Xtn2DNw=w7<2Bz<S+wkDHZZX5v)5~k;@yoYkF>;$z z+#MYqaTu_VtoW~TLD;e)@>()i41E6jDJ33875QW8mDC+hgEcWox@q3roWn3APN7>Y zIA~e_yk?UXdfSv#ILOksT2c;NZrhbU^6If{RTyV2R|kLHUKr1*lk@DN0ae&m3)r7a zrj*K^+cVYX*9Tf-YIfDFO0!ObW90;_tzN$E9B?66`e`@IhpzbVv7LVUB54r>-6Bx~ zQ{%a<wLgs-PfeO{2Yc7PyDfI`kzhCYvQ5OmG=e#rL7XXQjAJCkrrkq#85qb#K6lP@ z&t-h2=EMl3gC|Lk_5r9C6p}%65Gbeb#_Py#D9(}%%F5to7#duvz?pUg-NTa>=`&L~ z{NI+VFLgR>xx5B7T6Fn1RMiK+jRC2X7tI;DJO1oS^{q>T)Dq3+^u6D;4uUJ#MuFxL z&kvDXMk5d8g(m2$eAo(l0KnZJ{0E=>&%)GiZ1ey0*sMp1iHUDpD|2(D;+dS>bRuHc zw78n>EqFYaRs3i7n7Hrop{m18hA*ri^k|<i#!z!|bI(EQ)auH@CNqLnTJjhIgr*$6 zH11xjYw=d<7~dcXK25i;XltPKn08}ek>}Qr{k+(r!d8DSdoDD2WHQiIt#%Gw_)u%3 z!3}*M=^CFCPC9cN-(MZlP8@mWFmKsH|6FD@jLy%*hj9el=GhsOFEau;4Chm<l|+lA z+(A~5wH}r}o8ZK^<R~M#%a_)&I4zG)kTClP;COnJD0bF<o{~Ec$5%C!VNh*JKH+y# z(JJI1W%^XrTI5v%z0DoRV{*8DZ!ror-gC^LqjNl=wrvJGj#E*6C0kV<G%`+k<EwlZ zI-LeOB|cfc4!oUa*?8<JVwh4EW`K(k&5xQeBpwK%+w-Dk#I>(0`z-OS%P@iDZ8%pp zeic2D$wLReYM%U_heizuX>1xZ<k_3Qn9B*XOJ793n8oc!k-v1ZOo+Jfg*r^leinZl z^#Ki0Zrir&yov~EPV`~_x3?n)d!-F>opu-ukUCwXEso9ce^u7!A}~$UScxHTLVDiC zukNVCR*l;`vlV$AYI93uC86E{`?%gSuH)uAp8Q}<yA}2d)rDPe)7v;nHAbilzU3zj z!tIRo8U*k>3r{3rr5pZWU{r!jiJ`>U=aq`1gnS1xf`7G5P~h@{20NbYTkQB~#(6MF zv3oL7cnp!pbji2bWqNVVbY2|ZW?w`%YGh$2vTM;xF=V-GwL~{UjVVQGZO-3p2${FW zH-FDRoZIBDob@y}Kf3&-#(v&@rD4Kd1i5su(96!F3!@)!)cBrq{oL8~<038`YR#ra ze7$rZ{Htp6*ei67z3i_2d8lQFM(4I0O+N2;X;7Emh6n7EnBdmuHm@w;N>I|*o=CLq z_ZRKuO}5x#%h{a;D06nCr=510^W;~pa8ZAb-HHSKWNF^h1KOe=OgJl=-kD+4g>1e- zBE@sfF?T$Omob^fL#R+Yl3pdP@+{#LrX?9Z&8%&wl&vBb*k@kLT|)aH{qoU6xk)}+ z7aFPbHXnOEyf5BqY?kBXCDyFuXwH97<8z&ndC%+rC(=7f1K#i<CVSlAt)O+0sP~4; zBDx_}hmK#)`d1YBoB}F$-(sxOO*UGI{w5C+C+*y=K}-YTBI4rj#?U_=NNl<18zR_d zty-!ht-!8Py~9(GsS<@r0L&y{&QX#!a5{N-5%y#QDp)Xtrw~*^hQZ?Y=D>nayvyBH zl!4@3(BBNZ9qqWsUJmbg_7F#h5|uo%^X6QS@?yDyjL@*3ui#f_@jU8Do^ui|v?Ica zt7$<g<3Hf^dvXv1aQJe!o<W}YBC{5Qr(tj_!o7ikokIc~_IS?I6=u+tMnkd&e1?&W zhF#kj881KKFN3&wxR`N;{%7e{Ez6*$6@;*SzBrBii&L%&7Ynp2SVi~4DRB`Ed5jj0 zy>ZuUX54C&mpGP4=AnCb=x!gj;G;<1+6HT&GUCJGY=KcDCAP$_>h?_f?>S%_oPOMs zxm#OT^LXEL1X0|dH=2hunMKO=-~^Q--I$D)?F6FuBuDH#2WGWnvz)9TBLBv6yC)}* z=q}!@<o5o1O<2M}BU)i8tJ8BWWdNP$*GsTOry)3<tp-oBFcjz^4%q$)51a{<r<<Rz z&h^n!UQ%x6AdS_p3cVE7cOi7}bf#A~WX0YIQhu*byXW?Lb|Wk<mqc++tgI{ha(4Cn z1_{m0axYw1`tOUwgfZJPj4FtZqd5{9;kcC)JzXMd#ncgQHiWre?PO+VHks*23mA(L zY_wP|6;{(FTJWM3ZE|^KFxx|Up_l$DHo%T%4Kf7Nc*bzb$n9d9wB5?V)L853-4_u= zh7nINffBR$u>@0Ff<g2My<JN$S*vB>9oFc}ci5$dE45CTRlVMev{_|X^3k5*;Z#%U zI8(`eP%W$aR)0}BadQ?7C39s=zF}>3hxO`JG1#@5f07rdh(9@>4Ss_rPS|n_LoK-Z zxDZSh1APSA^pRpUxvQE&;O&2hkNy)J0muA1z5fs2{-P3-6jIFPQ_PF9vyaU&p?wRE zLVa4GMqT*ZaX<!MhI-mTpDTC)67K<uKG&1>gI)C#3W|$+f$VlLx>;|hZIjY^Y1}0` z&sr{b4Z3gr!T$qEzZz$~(c9Zw%Hgx}^~qiA`o$zt<L=NE=Y{K{+hFg#(oMEs{@uIj zq)Zwd{9{nRO@8#mR&RsJA%OAd$XxkAUQwU{zY+Lt&^mN#zxA!-+E^;-)b`L|AQ7Yh zzCICfEnRO|WI$huYwQ<zcrG$xk@6t1lSc*s>Wl6ZR05`AiJjV*rgd2{DXr|1h&x6Z z=``2oo>Z2!TB_-dELNQ`i}?m1hFWv2^cK64t)r-|e&7VV+)Q65OJomnwq-Q#xARU< zGkPs|!LrT%X0Y-Kc?-fa%B9$K13>EEQV(2~pYqV5eZVGt@aUpy@TMC~s0-syB>~`5 zHfI~2Ytt)w#nWo{hTRC*n$>mqFJ;>J8OyvXAOW@Yde;ftG+KG=uuzvj*C>m4ttMNg z41*;+qag}3|8!NxT<zKo(+@aiy!{2qT_xGqT^UYrd}_T}wAsAV6v_UP6BAi*CRcAZ zJ9(+mN?Wr5U_IE0ZKNFAzluMUj%K9o7)>Y~{NUS2?hOIa2W>gSIM+qvewaVibzK=b zU4}$)an@1bAbAHD(0)`1W*(I6L6LA8)XoI)<_Gf!kA2MB<#k<p;2`^^8bGWnpz~Ds z=le~NA+OuAIa~gL^VTyvv%VdeeG*!A7zT4Y_P_T`JkHB})}*%H=a3xnA9SdH40T_^ z^Q$pXUl{l?e-8!l|J-`<I1g*|J$3yn>2JT8YW6?4%@jvNn6E&zVo%N8!S((Ls|L`_ zzVGo_9Q&Vz0MejiuCmhRH$?5e87~3trhgZ3b7-x_zmi9uCMg@bxPTd0%K$342cMmX z@3j7H@ewltpcfUWx9a<I&Sqak89aRjN?sdRP^Wifqp#yU23PFktgEX{!f#nST&pQ3 zB>))MMf`Sd&{aqq6Q2Z@S=^!zS3@E`&=<2oYt(J&+#?5vZ-32%6YfiG_}bsoW8F`9 zbPG$M^^kvsg}bnW2hEX$NF`)7N&E{`y@eF(ip$3rVmz&jtXL0QW*-*xXgDVz;P<1K z1e;Ugm`3`Bd|!BMJL6+oIazsb^&$ea?`vsksgkm9TkP1;Huk~6+iQntA#wC{-Oy!+ zEta*6gQjras3A4Ty3u)dN)jVeA5SskZp$Eu)5j%wBOvNweAfaU33F_EPq`VuL->>R zRo*H~%F>z61DW7^4{;6Gh0tJ`*+=4Z$K!ZPz7tp5L67aDK}PN$?wn;QLvM}p$(@Gu zDWkPPxl<Sk(BE_1fxhUcA-VFr@ATob%MPbxsMW)dhjVGUXv<;j9%U*!n-|pqZscxh z<|y;Q%=eR+;*%wfH>%M?Tbfa13ccGOKI798L&@*0vbfsV#o4>a>U}JMLk7;;UumQm zHJ)6(1<yA)+ql?(LYC+$xN#i{c&pRm+Rc+fF5^wXmzkka7iV_8Ag`JiV@Es4or|k& z$G$E`c`?dYR4$)*Mw!KBMt-4Kqyn}bqnd2>$hjR%5;;QmY26^zVV<`?kS{9^y7wrq z;s$bo`t*q}@#K>{`ONJ*`UwYsz<|N=Y)q+rYY#fyY*l5z$;jI@R>;3@@Kj4N7Hb(3 zs-?V0I}~q|X0yNY=A)som%L$6?^Z%8MfH1GZgstmBhcs$yLrgzolhQ9m!c`#q?*1G zl9v?z&MF?ZWimDuNQ1bN)ss)O7;Q~O=wam>(1?^_y|3M6JM+qrFst1H*59<@fnR8% z_}&kxX*vqt?_W5I!)K;WJuA=0fGPi*wcT1}!;>^lQd*dXu)r<uw*UtMnY_qXYY4Lr zj#qK4O%VPGI<Fpqf_&ar!ZQ&KUyB`2Y^Hb1+8OQU>B!HihszKjIl#4RSI7-Yd0}=Q zS{Cp*KD#)a>E!s$h?hQM@TEw{k{M);l!+`M^oA`^r`&>ZRE0;Yqa|?k1&^H}yx95# zN)`P=<Emgkp^HV@4(Cva)t3y)Fa=kkg3m~;pOnc<<B2b8B+$78@+*{Lzdsb|8y-I5 zb{M3cqJ1{H7fV5V(<OU*8QMpQ2ZB>jGP>{PyOxaBtIl-Z%i|%>cF+A28w9qwUyKmC z^BwdW0X6&I8Q;d{(gQsAkTfuk?-d6;JgWpFB+k_y+9JDK`(}KlDH8Ls3@P_&*MUIM z4HCxbCI(sW1opsIr>1JE2d_P%Yez@`@lgKK^mQ8=w`wXhIle!9WT7DmUlO;-lSQ() zmSTzaUK~rY^KMJQf33{NWpdG0^r`CAKx(BUCKB)|YKDLs29Ac;Vzynx6svMCqGeX5 z_;e|rgqVx);>T-IB}mkfW-rQy_uC;LDEXy4lz#TL%wAzrN(|E>H=$u+F&6#WS4)v# zgW7nAFx(l#40Xap&cU%ug4!4?ja*7VEhq)7Gz@flb5NbC@xI(^xyhV^aX}U2AXxcZ zmkWkd^>6Ka9trt9g+grI0T;oLM?ciVdx`ELA=Cjhs#i%mYquH(XD;@Wx*JitwIGit z)?|ve?skK%zE6plr?RH6)r@2hq5qUpSh8$4<I!D&dYXnEghLl`gH*Hjm?aHfkQ`GP z$Ev<zkH&ic>CcT1<!;hfmoP50?nmbo+Zxq<++o+Ic+>wF#s82-{bEe!N>Z^e+{SjE z&e%eVhRF*50u_F+N=+8Dd4L5Z*ORst$f93)N3Kn0URw^&JIK$z083U1f$H{9$;$}N zxwyojfHh_klFff^M|pS@CU5vVvlnijZu+Cz++d)v&vgN>gwApQp*4Q~wWn2yQaKCX z8F}?lTZ0eN#E0;)2$>wbNmj_5U1Vq%wUw~x*@!`OnogurCXzOl7Q|5Jjrg05qH|Ge zhC$oqo0&9rn-qr7qs*$?3vWZ-Y<ua%LCj8_3diwg)iE+Vypi-C_UydZ3UeEba29AD zssGqBXiuo}rM3ac2rIBk@MTqB-4ZV70+T%-8j1%i$|S?@3OaWHIo3+b^p3PBG~DY6 z@O6b&+`e6v`j9EVP)G&q_4w-&?sY&@p>6*k^Irc5g8%Qo(ooj>Hx86b_E*jegPcY{ z#C<(MgVki2dZ57WGa2CLr?#%FMEsQ8x1A1Z?&Q$l*&6i704dJgG(4R5h_o&h>lfx) z7oyy)$3hvp(A@$I0?01(_g^<+dNN#h2Pt>`s|~*|1sGWK9smx{g`;rkq_AR9Am_rY zLV(r_mg)%xyzu0}(pcc$A`%MxCqfRCnf&_KIv;=(r?0)Ms8H#n64ib3+g?Kod4jZ9 z;D(}0f#00f_tbE8xZ1Uu?#gKlZA*Bi{248ng6(HRBKL#YO3`berNP<`8<@g+qWY>9 zBh-0_`Kj7XcKA-e-s%vFyl6hrQPKTUUKlpxIO73{c`1Yo4v5z-Gf{~c{nq#CL-FW0 zJ+JFYAH4b3lRy#fz2vXOCE2TSKd`qzJNED_(wKGlNF>0`5wDWx5AMNx?vZzBq?s>U zvHGxcBycLQIAu?f^~eTL{mvRWTbUoKLRI*)S?^25)%zFGapbQPZ^2V1N6-*|mDlN( zrjcSR$<v4GSg*U7b}O|h&nN6xGV*+?OGXtV{0RHqxbL7!TvpFHu$(9Jw^eca^7V27 zfCg4|w}}qTZ(GZ~P22iFF9gtGO+Zt*#6Pnw1C|TfV-cXWw4ouxaZfh$_90*(Sz(fK zCZc*_??5TRHwD}=%(kMetnBMj9Zmm%;~uC1_1}sEX8~pgpCe5Ej|||q+WVW}{KtwP zm{rL7t{wQtYW`Jg{5M?ZY5h(KI{$xS82emP?aP7SstUBHyx3(2pZ(-+dY#pbj>5%? zB`hm&6Cm<{F<9*aZC=qF^ovb7BUPh`6@OM$S3kIdEnm=BH#g>A#)ZJY&uo3__U1y< zIqoLdp52iLF2B|8wVqz^e;>ya)gU^Cw=h1uAu+Wrw0S`)OGI1=nH4||L~uJ%!FblM zV}I8u;N$q!o@<7Tgdc3c2&AI(p)eXhGwJqX+W$o&qnbpeFRKW!Vll5n>P+B460`on z_zji&4Op3RtY1wgyoMS=+G@tcR89B>Sb_h@Xz`BRZOCTC!Vi~n+qnSEW~EHL7c1gj za#f_*9)RQ9nP?LHZ@y6MC5I-ZOcDjSe!_Da;*vc3M;M+aT|2T>ldAVcwPCf{@&Qx( zS(tnwug9QqG$qD)p;IsFMhJcbY)||$w4MlneehzjSmng1eMF5e`IDN=8&(g0-W6T$ zS!1ng*_<!m2rJn73$!5{)J=nxK>nbJO&js+><66+hDt|G?`@DWPYhKwR>xv#v?u)| zTVWA1(4w9)lY43pwwj=KX8m!0Oa5RqA<CR$$qKMRr?31<BQqb2z5*^42RisfYw^Mt zGO$W^Ms5r3kAs-C<YNtiv~8ufE1|VD?`hso>CEa7C$A&uyi9z0rZA3sQ4db0w7*3I z+N@to12M9K@yrWg@_%~P`!Cr;O=o`{s$d>isNbR7`3_dE`YRA8od|^O<!sOx%i}EB zfV#ev31~bU8CAF`J;pzDLeza9pu}Q8y#}n<29(-nW@kaOBPbkXY46{-aeo`ILDOu& zAHXt9Eg)M1)X@(X|NS=KJ+DBgR5+IfEo}ttK(Tj!tYeIi|Bf*r59#{ktO8b0;K-pD z|2ZsiakSy>o&PQ*LrN8N{J(@NskZ9%s)qT#OwgGEmi4KJvb?0?TFZ%>U^m-+ul%bQ z0Qhm+IUuM*q$PYxo6-&1Ej}ti+Cqf9@DONtECPc;atg{o1XB;HpFDXVlGZ>)|4l=K z6IfRin*+s)Cqg2pj(=5!oK#=q7wB~W2^gf|Y0%*@0M<S<DM9)`g#4jQt$-rXNtg`? zBxn+fS-)fbf;agYfvHfNms9vsf5;k-4X(eltEXq*m!OZ}k8{}Jh7%Pi6w$?5)ZBoa zTzZw$;(0o%$X$iTaX4hd>5TV{7a1;Po`agPXQ)X?JJ8Pt*0G2L23u{U-NW}2OnZ8I z-mEN4dGc3Rg5gF~)qrb>8EAEERR7f=nal8|($K+`B<)@tL00G&)Pwp@)@2QQRtGW0 z%8B<QT!n^vHwD9c3~84Px%P&lPgFp|BU$uLb&W->z*|gui8xhhZb^G?_4_WK{R${H z!Y13s46b<Z?8y}7lyosI7izAInQ?}QSNi`#eoN$`U$6F36#CEzvJ7&FXG$^Q`4W%? z&jZ~nM`~i6R)9SCsg;Rd%<T0&&`U7DW*YGMb3Hf(%sh7mJ>Hy1F&T<l<!P_*PLBp^ z%OO?ByYY1QK(PnRK#s^y{MtxZe)1}BE_0-lJ|Ki82x$-q&KOD{QJYti%=`$H%W{|M zGp)S8rnGye{P)bBWQ?#!yzZBta(U1rSECe2r{=9%KIrE+=e+a@F4nqB$7t|8n^D08 zPN&J~Cp1bH=>m3%ogGd`I8tBgYHnw;-R&E*NSTHkqT^QSiEQgcua)7>RtAa2%6;Ly zzz*fkuvwkkv4j{yY(q_EFCCY}%M_m#GClSilH_br`6l1b1hoxD%?H73r9AJSrbB%6 z#8jdO-RYJAJH~_VIm~MH3^AzjDa3GduU$#0k8g>oi|=I(+;3ESz4P`%HMyToti8{j zV_ZTV8V`(!IaO)B-5+myb4`Mrf8?$QT!ts^zjl`wJ`}D2rpary@rnJe*%%LkkF9d# zfz3y`@!qUiYen&#u>81DfjT`<OOaj*I?bG&4sPTt_j5%2InpSk>FeQ{SsZ}+ktH$W z+zYlbsNRNT<`zP3zT-jsUS?fbbvEz&xm-3Tmn8JTnYSVG;4VC@KnI~QNUiz6&aOuf z)b`Oc89&a!hjQ&vvrj3jUYX9rZ`1Q0CBG}X+n_n>IsBmUhI8nH!anjTtVePUM&5N8 zQB`Hj_IqOf!C(1+6t^WUw2eIT#k#`pb7>-wt1fA+^x!y_C_XACX9d85VK{{i(!DKz zrAKIHI6aHoIlqvF5trui>vI^A6z5*tEF8N6q+~4CZ-MQ{O~l4zR3TrG(05xY3`!D- zU%}OR%CkXfHOFJE*0$BIML8S=OH421icc;VUI;fWEiJvCv|WD-@l~9cIvl7twn?D> z4J@ECi3Pn2l5!nOvM^J*FUJ77%ThftDyZMO7u5YC4F7|wF4d+kcfz5Xvax)4)<<fZ zOxv1bSu0-ysw61sf!0r|RGYT9i8lDnj|25<f279CH!E`Fk+ox|i@SDL|2exq-j|Nl zyuoRgDhlT<T*2V^>J4l6tN~3hv=gzn3DT5KEQ~Lz;MaEA{OsA<KZuro&5Le{``Vxu z%&VM5a>ON!4a6RFR855Lv1w#XpeJi~+7Uxc;JQEC+2scKPCAo)j5nt<GC*mKYFm}X zph9xR_8cIifeMzqjkl$NKf$L5tqmrO5;?OuVTj=tRIp6?aYi-9kHD~B({vb^<)m<N zNpw7Ubu^E$#Lnc_lIX?ab3N?0=)qjZB3o$r-c*i$EH|T||Ksvo4Fr0p^Ma@e(^Cdn z(D4AFE$R{+tauQK!Eck|T;!JrdjR%F-})64`>J_#4oN{~i9O6mF@s_=$t?BL7(kbo zI5Z&VKVdD*f{VQpC)+E7i`AiRoA?_NqVS=YUN3&eCugJ;J{h{Vl7cAgWjC3O<uPtY z&WmG%4_?U|8Kx8+zn;{|py0{Wk7bxs1X8E9EN(Sw46#}6Z)UQck*yLC#0%%Lm4>jq zeJ-&0@cSjUBE+nPJ86w1RscRMmk~em>VcsAvoLcX3@%Dj#;7d1oifY%>nUBZPH+L1 zDh?cQMS%u8W+h-?njm;<#<+0nQomO6PirK6>x2HUvvudW<b~O<Mit~ofV$mRoMW+i z)^eA_AC2ni7ye9-m$x<91jTS+UAAa*tGCba*zv{ANA)Q2!A4sRj<|p@DJM&Eb>s{8 zrCn~ub9O9Jh3JMP#WM;JxmVe?a}Qb#@_brM)-+uWS4@XqQ5VTeH&#v@@KXsYT+XnU zYeUG!_+EuaoLtxakDXbW^mh!O2kJ%1^UdxQr)E!1t;ezxT$yIZ?*p}fTg7h=iWDRl zS#gJcWry!rB4pB$1*m3Lj5u|nk~ezXaW_%h1F1p45MmZXM;i~sJ<8;Y7oMEWOrjS% zt<-z1HI#{?tKi>~zJP{TYXB#}OJn4A6j(F$QEC64f-Ik;%g=B(N<M2%-EekHz6?@s z&p!%=2?QN{U&S>A$idRT8K(_RivxrcU4m$+Bd5eeT@L>w=%yL-9l{;!DBLvO0|6y% z^QM#o5=N&-->N(X?#0!-<dq>$Jw;5o&%gb=X5%$gpjEz4MjO30B@mNFOErR1Yc#Ge z`83{hZFEeYL?fmv!-#3`p5NzjcchXi`Gxcww`zSW!Z$Y=z1SFEVW@3|=Z-H(QkkwA zx7&6br|X=N;)dE0<B9Lz#>W3(n`BP5u=WZdb=E1KZIYppX~s_LIY@$Ue>of`^u{-p zAet}4kzYJ<9=n87KsGWjQ}++(XAiRO#Fiy3b<(E{sxsp!_Fb+5HZPWhIP6(T3?7IW z@P^xDy+Fb)0*+uuy^Rl7?Bk;sg<xtxH{B7eYkdn>KYH=1%!*^~%Z7zt)@VG_A$)Su zxH4`rLq7g|vJt1gDuxn_Z>K1IyAz9<r!;R~2_z;<{w+}nG<_a7pvjD5u%?%e!tC&d zM~?7^6{lJ_`zKl3BQOHi60O{qIprEZ(NjJ+KWt!L^}QtKyIrhR0Yjbuq9Mor=1Q1R zC-1-_=Y}sQj~CdqZ-VjY)K>X3Vc(k`K-D1WIItpI2<n2sM(u%bRY7#X;+^-n%9N=6 zK|T6~1=@G018@{+JWEL4Wt%g|h}Amig-~93n&pH$KhPa+`oWVsv_%%x%RZ8`>4eSd zECDH%3;xkZ?C?OKet478Uel&oiBDK+^raaC+fy=r1;tK<TdP+%na2wGKmBIjm^AII zt4G1jr?<gtD1kN(Q7(oe8_R{gbzEzXSf-Rdl&d_zAnyS<${9+|Dx*^DfY+i0{mk40 zfqVgB9s@qSFZVs;P(((7we_TUQDmIxnUHPPMU`7oJtw_e+^z(hejqkaA{OMOOAU#( zH`ZR`vq<+$k!w#%qk8qFzVhX&%%v7Zuc<1fV-4-Jw-)zOQ1kK$0CdnaEt5K1W|@Ih zzcfsvXsI}D%IssSXkLBTeBRSqB4?CfIZU?TTXM0N(WRM)yELQ?IupfIDGWn{VjL#3 zmUc+1%XVhz7&3%*vT0ttHIudNo0AK3jcW#akmM80Z?=ak77bM^&8dFSPdFtb3cSPZ zCin*tU}>+U+`h+9lN(cjYkUH1FA@eq^xk;zPU85TT<f+~$fqLq0Z!2aR%PHDgm)Dl z2U}p*{%Kt@R0G}dya^fj#gj)af&Xy@e%p?U#W%%+iVY0*T}yQ|`j2&}{@1&IK>eHa z^XvR}-#`4;G=Bf@8V8fDCU<rn#(&+@uJ_om0Ak4!UOHdbTlsE6>((>*i}}CT7eEz5 zzo7MtTz}j$4w`B+!9SX@dtvx={U}Lj-2>?Q2ek^ddpmj?pqsg$mzQ@QtbOvAHeTWS zuOBL4q$373nEOV>&#fT7u9r)!v??x}gP!%2*^AB<fBY761TKK*0zmsfbo1Nai(myz zz@jy11@m^RcUx4z*(0pZ9o-8G68_pig~G4@bE_h-h?sUL>(A}hnlINGzr?Tj=c`8X z>+d)JulCM6sL5zs^w>oO>>Utk5)f2+w*dyE1qi)})KH~&P?~h5MWlBVq=P`Hii&`= zP(yE_Boyfo+TEXe&YgGW%$xV--uvI3VJ48|``F*!Yp=c5+H3vxzvTM{c?q?I{7DD< za%tX4_{0k+Smg8O?$Al-RXOcXKm2D<;qTwdc3JLcFyJdx$1nYP&VM>bX&?r+^*aW} z6k^Z%JN6U>%#=U38|?o5%Rgfj9Y5jc6|$iF+_@ipOc-k89qP{Nz5VqCF%c}RV`DQP z8+rQS(a8Jnj7~B6_@$hBb;>;2MfrLiwRT~CiC)7%ieXMkZ+49}1HL4`-oCswt(`{U zHq34<xa5gaerWL1x30%eH;TROvK?kD3|2)wdD(6$GSa5S1)H9;M<r(%%<Hco2LHA0 zmfxSfuBDNmZ)xZuSxI=d`X_>Rg6WUj=XHYP;{x<+-6<#JjicZfgUDOUz?%j62nBJ> z&};gg7a#@%xro1|E7Z`GSq6HaEtUPxJ<?#_o6<#MMO+bsr7@;B3Ft$u@(=8G?ms4$ zz>nDiMdQK+wS-v_s0n7D(*Op7Bcg%1Y#{gR*T)wwTuA)KC-44dWB+OQ_X4hvKe=_M zdK3rJPXCS}CMd@mZygMN{AU;N^25J@z~7%;_WzrT_xHJ({?BLq+x~}tHxU2*rOKu_ zXvC<r@~~JKRGa2hsY@I9$f+{%_q|7d21P{lf`TFsrf*}mcLskCz2^VZo&O7s`rk|c zw}=0T|9^*w|4&KNS;s6+XzHX8u^&Hn0UDc8*?_0_pOI^Q=jV5rhCxi@&vc><^d0+c zTmJmN&&Ge>)IP9~DV*5|&EVBNvU4gtyQ$3qWLQZ4Lm&N>l`pnDR6_DijD&gjM82Hq zD<7$=Hsh%32rt1z&h7{j@Vh3Mra0y1H@)j6uq$zh>UUG>`DodQ5p!%GfCjh@G{j{t zb7Y%O%GtcKZ`uNb1#E5>R8(2OPp#8r(P)z`IDL*pev)^LYV_%IM;@w8_D7>Y$Ij2d z5BOXRfKO0Sb)G>`rTC|2AE7c33HV#_0yOWb^@w*}t-pAve9SKc?HfQ{=3(1O^!JUS zo}SjDqpFiU2)?AiY;)r^-|@!?Zk;%T=~qV*V}tdWsf0v<AdK>vH$JOV-FZqiI(Prc z0UwpGOR8IOc|dXPpMUo^!{=|;JuAONmB7Zz%39OQ!NFl#!1in0^dOb#G4N~&((L`G ztp{IA5Zw8k_hSczHYD5&7I~@au7hjuJncN)f%iQEMiZ#fI?XI|p8Ew#=N3FViw@ra z?FOA}dy)Dg>?)(hf(J{(0V<(R@Rq?lTX81#bFkyLsC<fPpeOCrQT)-CxGI1D0M)y2 z(DSBmmFg=L9?Qy}cmO(uPr&}L&&40tRKXf~27JBM@a^F+CFA2Tfcdx!l<TR^!LM6c zm05w?xY;%NyQ?Ru3~v5z`EcE7^W(2GbUN>~cn%F##GY505kBZsd<g2dsMxMeN$p>} z2f*L<|7{DyzZb>7vi2`h{Fq-{35j%KBS8xuRV0uH@M7@)wOAcohlBAFKDP$lq5UA( zBj+ByEdFN&{+IuD_$PFKv+#lEv&FahwF*m-M*_)edJ{-Pi^_W<-_0yXgmmxSP@t{6 zJtuDMlBXnz#C3CU$SMxK%c2bmS3#74l$^&p>#2bZB4gWtQlSfSv!aYO-vf@$Jzies znomVVh-0+Z-xzB$H3Rt215mH*)u1pL_da;_3gc$I0O0OiTlQFFctM$1Jea&T+)#$n zA1i#*yEfOar6}I79vPjGFz1|CQo?7Mm|!i(`KfDW#fqOY_F5&g+qyLTlllE#4PD;y zuz+<G{VHykRbh843SlLo!GjiE-RS3RdZtpVaMx`VZ<H8ycF58-!3!D++lT8HMAtUm zzrpMW_>63^Y~j}FJ-PJQTt`r5jH+H$E8L@~VfJk0k2%_0!hLqm0O8t&)~?vwMBGZZ zXH(@}`JvAKojHnTY$jB)OR%Gp+$mUIS>XpLnAeNdRs#Y6)+GaF-5G1fLIu*n!6#_w zMM?-3oG{pXnp=?&I!;PPhV$;-j>Sz-J;$7v-TO0+w_al6>p+ieq;+{wvC|v|FK&O3 z{f@i%cDo&9O!yw9Sr)C?FvykGC&*0ewM#iQEE$lNH2Rk%kF9;%KGI+C%oorJ#Nd%@ zoy1RJW8}TH*5_^}t{Abx4w3yTr>ic?3F8y#s=D)t&vX|k`i$qz@jkbPB=R2Bl@_C8 ze+;o9ork+smX=xq!?CbziM?{Ah#JTH`>t~A-@bnraHmn=fy|!QeS2rK7u?dH96Fjw zq9oAT@u8wWS#dg&y0oT~ld-k=Vrc`@jQC`B_8jkCzmyF-Z3F@lDjjOZ)66EX?Q&mH zXIM6Okhtt3c7QnVR0-nb2i>l`Uk>p3%q!^6Rc!5qEPj@X0jLNRsIz~6&p0@L!)ST3 z5vn|8IdV7#fYSE-7DhBQHRJT3EcH0H*QX|9Fky-U`=Q)uxz6OP`L4VYu`KjCwECa0 zO|=bQui4#eTjXjkYMe)T^kn^9sWc--TDgsDnm0B}(l3nV8vNQQ<TBqQov5zJlPxV} z5VzH5{Pvw;(KsZ`(d;qGeT@{8B6x8D(ff55=b)ip>uxDL*Hz-~CUstkg+oF@YyVzN z@szkqD%e2b1VFnqvMov#i9~^#?V^GC8c9grPQP_SX9f;SVD|_Z+-6#MWQKvfL1leb z2oLF21Z#`O^qZ*>l)9av0bS5QB+0w!=A(d8^gz*Uk~1wl7|mjwA<Sp*kco4e#{xbi zCkF={6fz*6J`Fv4_SG@QSr^~EccMEhkFO$-mY9yY2w5Z7eltp6d~{qKrkST!2V&$x zn_@>VL^PxW_fck*A#A>{kl9E#@g?E*$EiobDnv=ybnZ6%v|LXPPRv8I(fjyFBsy>= z7^6@24;+bQS*uMclUBU?ac85~d_|q6?U@woY^t_yQ(K!&`3IRz5y^emIEj9(Y)si| zPjmjfs<~#hh?C{zp0#fz_L7~%7!;#(wHGg0frfEdZ}H<u`Werru1{9lBjR96FD-RE zkW5bf+91<RG&j2=xCUWz0B~i+Q{3d|Wx(!0f=X&-zzkr4L!}6y6gLKqX0NQ?G9TI6 z+JZ<uB|z`INuCB}e8P#XK6<5Dz)xNUCA}743?S5%h{H?@RB8*z8_Km_yORzg5Jb|? z&(DOs4vNOj0A9@vpfuil2B@$yF+Bi#e=$SW==!lY^<y`H7vS@QYd|#@fz}nAbok{T zVk<rE&>lN~!ELv=`n{9dh|l-8&CK2WxVDtk`PIhdM{Z^&u_%;|o7*rFs;35I-k637 zWkbWX;NW0YJv|2HCm5%Ie0>%s<Js@9>Jl8HZ1_DB+|9May41W_9}L!7Ok)#jrck5G z<;DFc!dJ%fRsy2u?T`6!MQsiI5*G@aE)z<8<gn_a>hVEA!L%Ct#S%U3&?AQ;I?pX@ z*TfpTmXLPI#$^Kzy}q22SagD9>`Vw9d-FsgpBvtLwh^6P;;{ECAOsia6%jLFQR<=_ zW}WyxPax<nD*k$UwSTEB+#;6;xr;m&E|H^`o-w4~o73tUi03_9YG=#|U}x<-8GN*L zVMAgI2S&(8n)lD!N(`>X0K75+;@5CB<3UC-U=#9~cVpF{ny2ycaU;zh>Wx-@Qvfxi z0Cw>z04oG=*^ky87y_3R5iH;HnMblceV0Nap+KK?m+g-M)I`qUpbo%6s(_^|yCgjO zBv_uQ9vcy|uF_nN#=14L@y}-da^WHzVrv@c@-_&r8CFWY798AF_*`ZrPQS)QgPXIU ztls_SFie(PuI3IN02Gpk8_u=Awh_UI!C<lgsNBWl`;^lKL;5c5NXvLrVg#9PibMkH z13o4J-!4=;h8AC&mZ~&1ug$wc=<ZD)w-uUn+Lru@ImI#j-NrM$Y{u2w;4)#?``Bg- zb&r{~n5iiNl%hL*O}CNQmyWzS17D)!kejCK%@mxrFy500qwm!9-Oe@}`NVwH!@B-W zRm|4*<dVzk5Bek%cLYf%c4|i*!`(Xnu{;AQG8K#4knN(_G*1>uW_wDt&Gy69n~J!q z$y-Vmf-MMX%2PE_D;Q_~uY8L)ZAjmV2f}@>>hD53z~k%Mem+u@&wKA4AE1K*jFKjh z=x&Ii33PP3pb*WVby<?jIJL2<DH-5@Re>T|9Q30;x0mWD+zr_zH*Ut$_E_NG31H3f z<=cx9bfn5K)~iCdq|l*@<IZ`WXaTLQ4YS?Ni3l$y<CW9^>+q}%JGXE~d$-B$`4Z>j zsvnMeWSk>%ZsdXRr_zXH>UA#1*?kXHtX%$FmR~foh9OzNN_!Tkzm8A7I3L-7no$P3 zzEM!A1Yj~yhUzDl61r@9>I0&svmdUR@JlejGM>=bJLzzvB|OOacI*|zTh4r<?3tJ) z6(l@TZ=cKtb**rE<!sgf+WhkhdMn&rjA3`oHC{qb2sf6-@OiOzbpYi$ZrGJ>qdj7p zvQg1UG1)EdNB&~h^<ImSmABHMpCjeuwTpVk=N8Fk8`4J^x$a0l;Z__qm5VeIl_+EF zbM2*`l(mWQ6HE}GXM=bH(C+RmJ`+r9d<Hx!8ja2dW`L4zB}4L~V!+(EtzV<_?b|mT zg_Hv^lsLG#74mEMa3(+BF=3vml>jo283@UGIcfk61$afNfV{JHB>FqRXrutaha=qF za4ZmH*G?p{>BkQUK@90MGRTWxx`ubk0?q-|{l`ranuwfRYJ#X7H?dd&%Ud0XeCaF} zw9LOsCGNVbYDr<5%+;l0yve--mp_{07aU!|D7l7jB&?FU<q-ADwepES9&afwEN1HO z+-zRN8SJf&WRmcr@I*J3!+a*s&l>W*u;Xdbt&-bb_r}zdM^r!}L3gU}K1}7ao9E4? zh!Y$N5tEmba!H;REbvdb%eS}4WF~S9fwSPu&eE3-+~oJR(#rPS#Tp)b-2wBah2*rK zq33gc+>fWCqLYNq2=LtSe7k-<4{c0Xo6%KnS<mpOszA<8XKi}cbiriBc@e7W^KXnd zeh@Kemdp$`vvD=_%#iamR#!IF&@gYM65SlRC^15`!H-K;cmMLAcj<Jd5F=zG^Y%7^ zuHLgQSj0V1Ra2V<dPWV2j6F(w{j%+F5!Y{8&s%00KEM{L(BX!_H2okq<URq)l>pfS zFfOhxzz_kG69RCdb~cwFU`Y*y#MzQ4Drk0`@X0D(I`s0GU{@{%tf*vwxdnW_!BoHj z1KJ91)nB3v>=PDqz?OM0>YQh1XXhav%l5j6aCMJ)X^#e21Wyeu0%c*kb|vOjhLQ5K zyHWhx*DyB7qHz8zCy<C)00Lod_9n2eEnZJwr0qI(*ynrgfsKIkAne8HwfO+CDeVkq z7--vY0r3;#3sQ6S8g8p6X^&<~I<5cf44&*ZOF)zOs>#BnzQX5qUL3=?<PT2*-FG+l zU$w~v8I_}1x_4E%v2u^vBC|tO)l1|Ta1-YmE)90eL3pL2;^LT?m<t7Mjpf%QqKx65 zJVNRxFM8%z6m#R{6a`n)xHML)DG9xb<eI^>h9oC)u^>+@ON_snq75*P`Z|^lBnQ1r z&oXZbMpakLz~ylQGQcS2`q(azS(;Z4lUc0ZzT~MqhrdI!qB~I(?j7zH@dNL#erdLO zWBJgq{jb_n%KL@B5#Cc@{GuU3J5SAK+9DaOD`W^AsH3BEp8zOu<{-BMFup=W97!y2 zQWqu)OrtYOP5^6N6SM@To*SU#@p)1l03q~2qNp>{!=nlST^LIg0G#?4IrS796VvBQ z%e=h2ho&;B0BQsxThtFvIB0-GcnCDw-79!lF}`JLo4EsuE#8doVA85wSLMu0+&}8} z=zC)N%6F%?_0LY4buIR*<SxJ!q@=b4g@-HBecdFABqwe2-^*qvggp!{drPF@41;DG zU^8n?C)kJEY|7nX^w7>_@Qfk`1)aEpV3}<|WG67h13Gio&!106V2XO#%o#51tqnsL z2+QBc9?Du3U^Ca(v}?(LxP}MF7-)PP@?(f0JUxA63AJ8{S<0_Wei4Uq*+h}=<n}RG z?#{ALUX^D_&!l($h@#Cr8BREtmL~%nIw0<-x7+ZutEL6+X1e5y+aWze#Sa!}=@{BY zb5|O^j;$%vpqpu{Sq^gt?RrJ#>FIc64BK?eS=6A6N?hH|M{mWPyo7)-^dJR$%MA`; ztV2UXQ;ePG`!fLxYV%+)i}3Z&f}B=^%5RdA@?O0<STVHV<IVfoVPQ}+*^6RcO#A|u zm8EF*CVxE1Q*~!jIKIV6ETbLj<u4WYL=_@#^B^}~Vji$J=D;WL%NqSVx<S@6h|JB+ zO`q)wuKj@JSexL<qKw`o)a6gzf*8D@5jG_yWlZ-~iF#ES1$*b^7j5ewZ<v~zP?r}j zpnU6NM8h%83fl!6q8=$)$vi{3WcNAsUMJl{2ZJ|u71nI?RSFLm=1*hd7S+q$U5GlJ z1+~G%pdR5XSF~2wO@{X4PnOKgw*bwk8_TZ65TPBhQz6SaLeInD@WlLrl?pslj#~Ni zwH511*6d~kpc89#Gv<3#S4zhc_bY5&&!2XZ1}V8s;m8~_(^NcOlyf=>j$P_wiP(2c z1vR!?0vS~Rip1}_Yy>!1tY$ocowKSS0d*$c-!^T2DKzwy_|n&N5NghCea`jEqBEPT zyHeGA3HRtSZK5hq90<MdRXqI}-f5qVMzvs>uL{9-PM?EQxc6U=hpV9+(6CY)#?5P{ z=;_VVxw#|fXMY6v;&M7KU2Wgpe3Rzq$5Jt5J>oZF>;C#NKI$x1rmlIuuw>$cTwGA@ z!+_|-5wzL!y$3_Xiys1<BPpxYH3K`CIrx%Y^*hHe8x}B2McH@DpFesyS4k^sv8h*g zrRz&p?~XONZP$xOW}M`1jLE4Pb2VvuV)==kxRaM#Qk?QI?ot1-1kSxN;TUW8Clh(3 zEF|?B)=_}Zwpo_6O4#1!Bklz5GzrqwMY710*ez5ebM&>c-tIfaC4`#^`+nrU6chrO zUl0hJw@oY^u+IciUJj+p&;e^_%QWy<e9J|FxlrIbq)Uh40Unaiv^C*P_03_*5$&B( zM$FRb{H*$FmLrS{-J3VSGOE%+8?U#kb}Y;rr=%c9lC1Kb3%?<L&00>oMkFn*MP{{D z=DM1pM*`%lHdWlllxHX$JJF=JGyh=M%jj*>dS!R6wqWh@u(Z3BlvJH~hIH7%biLpx z^Opa|fynTR+w`{`VZtrm<?`||D%aE*RA*n>?V1=A9?kBCh+U`=i2~($&)TS2ICb~- zSfX4M8r?f5{-veTT!fuyM4I4V#s=&fCOBG`T1RDs<j2oB4{MIJ$FH^z-plpcw}T@_ zyOxgb3&0)-^6V&c44sYcTiB#@5GyZzuw1d>pISaIRVJL(PYKXn<doH=jQbkylH22O zFJ^3Z7L1Dts>m@_GMi}YK>t_suIgUd<&kum$Z$2$`h_UGhN@~bgiHdMdwAKPT?l~1 zPdV@Hth8;=&;jJ%d&B3?*C^!FORUE;0jwHS#>YV1I*@*c0+Y{cdSZMW^!)W907Zf< ze6O_}eT+;51F&cSE4MP>)Ri<q58e()1fc!L0D6A1veE3}!&l(M4h+!l9L&s*3JV|f zEK9({0$un;2Sbt|R~nNvpH{k>=Y-qp_N7b)+O7Sx&W=`i5gg?BVeg$bYaps*dmx?k z0VXa?`p^oGq^!?hTfLNOPEG5%?Di6`Rk;@UR;KH=u<RlCaOG#uQk$9&3C<M|EM?{8 zY5L9=RUGxOKS#Xg?&X+C636%RoQO&6l**lWuBwd4IdsHWL`f;@Wg|aj{`J|!s8MAT zliGr76Ru)OQIr<P0)rJ`5pzmPN{(+Gi#7I7aGr?w+ka?OeQ&J)^_CoW?~fb_rO&Ze zr0(o|n_>Tf`Pcy>eppx3NRwfA>2oyj0KOZ2C4EX&rnEUbVK?6JInKp=c-Nzh#o9I! zqfz9X3-gwn1oar#RU-a5&bV*UVWPmBuDrL7yF;sXIi;1Jp!p)`GkVJEV^*48y!s`q zDS~(CMOY|<xK>3)MN@NgC!mq-v<hFRZHX6DgjQ6OwxryyE>++MY`03m*x6-@O<M31 z5t4bJ#u5X3>I0A10cR#3VsYA3uf;;<50OaZ5Es`4(`uRK(HuAcXV_y!9=tgVE`!)q z4+F@+u)QTIssB49w!HkXwNm&>n7jP@^%R%=_3B|jdLzz<S8YwjuQjqKClmz(|Jqxj zyR=~ypIuq%k*#|q^DfpwB>MC`a`BVn)$UAbX3^eTcWc6c-+yUcu+XCCqcYeq2ZG0M zP)7ygQdkgR1hgueTk%Or-0>}0K=_W%361C+s(IlnK4_a?vfr1F5nRJtcoSITTOy5A zv_7gF8ldlF)y;`c(#W5jG|J4*7TmrzWxy#cg$22nB!JPcTLM5<u5tItsi~u$J?AQ9 z>^CAaL>aX*ZOZ(Hu@c4Vq1Kh$4?r&<?Cgq;?nb)`^k)F&;InKqwb<LN-;1(;!P#9g zFN-1~H@=n`hqQJUP%{uL@q*2kl(hmGht6N_uWF|0WOONO*S1SDg^Kvo<^<$CBT0N# z20`~kOYmTm@Fs5I@y_m|cQ3GAbHRL&;^Wv$mioY?7p6ZwH3cP|#)1&VpFoAx#==m| zn|mM$;CF=XwXvp%+v<nlkPtOBHP-A-%*>40w}u8zettDj=O;RrI{B7wjt4iB_Ga3{ z{l(!k+zBo%y2`2SlAOKK5=2~<dIcApHa@ejPXomM!HEJ24=x=zZVXKSGiWQA#*?>S zB%iCKCT%@l=oqYKnFU8AofN*Vi0TXd1@f<HG`<LFJXvDk;o?%&boJ+o(7t>3F3A39 zUK`FcHPVc=t{#=-Gk*NbXB_gOfN22i@_^gZKoLd1c>~OdQ>E5JxUG}#3juHzZ0|z) z0@@J*RP#*G5>mfK{w`rdBHm@g8Izw+w<RJds1B%&!8{p#^G*Z+>YO;q0pP&rpkM*j zW?aqY_{rb!Lnv&Nm<SF4Vuuc=`gwrvCUjv91<RAM*#dv|J1^Q61@g$H!J;DE*)7C; z^UC-IYhIc77PiVosVKM^6ic^Fdzd?ZfmA9O!k2WPQHlrJK=x5#?Skp0>dCzn=QvPb z{XN2_kEUeF&OTKyQ1oa*C>A?fc~;Qr*XRRBklRggpC77J4+;u8z*+YgVtvDSczOZa z@&di5N7x!TKH)=oxdx)1J0E<-u0jV=laNUWjRXtJ2O#PIJ_C*q!*Vqv9z1x7#bTqQ zqcb))T^#573aI;29|fvvXxxG~H8mlCR}bdAXMD`s9HFzU${0)<Fg4iF&CsyAiZOKe z_U3@tRLx=oC?-`?Rm}iqXYFG8h6{+0ou{YI1>ww`ygUUE8#`HXm`RIvkM5Y(#l4mZ zhZEgP9l(9iAY@Hj0d9GT7(;|jeQH)d>sC1bE;Gld4bR?zQ6tO2@<M{~K42YByYJ4V zM;uMq2eQ2AE^asZ!zV1_q4`c+qJ$?7nX8qlD7mW&wgqY?{QdoZV?Xuu@&F@I0f1f6 zT?Ho^0ascOIy?k?3?-OwSFa+7lao(S$El(5gZ$;WIZN^NzULs`+F$-6-3u@QEdU%F zs<~o?8yg$f#8^mBW|&8gJ{{h!0{A<q&=vR$83_s3b)5*tH8<8EQa8Yj_kc3iDeZ{r zr6vE9m$2UZdmzBs3+fD6;2{@pBTP)#_D!@u#O~SKG`5XIChMXI|9J7nCRdx^pgw+8 zUgXivRDz585{j>@dC?IpFhm+wd0*LyiQ!`*d>K^i&%rt;0!~PIf4dAE1CXm#3Y**l ze$BQX9lZ!&+J<#w@>uN79HQB454@ZO_b;7H_LTE-!Wc{L&w8!fGG#9HK6nI=>fL9Y X!WPM$wkZ#X1QRZ$@Hp?G!K?oO5!y_3 literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/05_vagrant_box.jpg b/public/develop/images/deployment_guide/05_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eb3aa6dece0d58ab688afab612c0c41d67e4ebea GIT binary patch literal 25785 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 I8{WA0UwDSrAOHXW literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/06_vagrant_box.jpg b/public/develop/images/deployment_guide/06_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0538bf85f81b221e315d86dad1b398941a2f4374 GIT binary patch literal 25503 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 X_w=HLq$C5mD~<F_|6Q(o=gI#8y6J?! literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/07_vagrant_box.jpg b/public/develop/images/deployment_guide/07_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6917ac9f9b8ba7496cea815e5581db5dfffc9937 GIT binary patch literal 26303 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 E0c-1CPyhe` literal 0 HcmV?d00001 diff --git a/public/develop/images/deployment_guide/08_vagrant_box.jpg b/public/develop/images/deployment_guide/08_vagrant_box.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c35c2f1f3b2cc5dfac6c8d41e3b880d0c3129f4 GIT binary patch literal 25022 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 EUl$onLjV8( literal 0 HcmV?d00001 diff --git a/public/develop/search/search_index.json b/public/develop/search/search_index.json index 93022f8..dc04a7e 100644 --- a/public/develop/search/search_index.json +++ b/public/develop/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"<p>Welcome to the ETSI TeraFlowSDN (TFS) Controller wiki!</p> <p>This wiki provides a walkthrough on how to prepare your environment for executing and contributing to the ETSI SDG TeraFlowSDN. Besides, it describes how to run some example experiments.</p>"},{"location":"#try-teraflowsdn-release-30","title":"Try TeraFlowSDN Release 3.0","text":"<p>The new release launched on April 24th, 2024 incorporates a number of new features, improvements, and bug resolutions. Try it by following the guides below, and feel free to give us your feedback. See the Release Notes.</p>"},{"location":"#requisites","title":"Requisites","text":"<p>The guides and walkthroughs below make some reasonable assumptions to simplify the deployment of the TFS controller, the execution of experiments and tests, and the development of new contributions. In particular, we assume:</p> <ul> <li>A physical server or virtual machine for running the TFS controller with the following minimum specifications (check section Configure your Machine for additional details):</li> <li>4 cores / vCPUs</li> <li>8 GB of RAM (10 GB of RAM if you want to develop)</li> <li>60 GB of disk (100 GB of disk if you want to develop)</li> <li>1 NIC card</li> <li>VSCode with the Remote SSH extension</li> <li>Working machine software:</li> <li>Ubuntu Server 22.04.4 LTS or Ubuntu Server 20.04.6 LTS</li> <li>MicroK8s v1.24.17</li> </ul> <p>Use the Wiki menu in the right side of this page to navigate through the various contents of this wiki.</p>"},{"location":"#guides-and-walkthroughs","title":"Guides and Walkthroughs","text":"<p>The following guides and walkthroughs are provided:</p> <ul> <li>1. Deployment Guide</li> <li>2. Development Guide</li> <li>3. Run Experiments</li> <li>4. Features and Bugs</li> <li>5. Supported SBIs and Network Elements</li> <li>6. Supported NBIs</li> <li>7. Supported Service Handlers</li> <li>8. Troubleshooting</li> </ul>"},{"location":"#tutorials-and-tfs-virtual-machine","title":"Tutorials and TFS Virtual Machine","text":"<p>This section provides access to the links and all the materials prepared for the tutorials and hackfests involving ETSI TeraFlowSDN.</p> <ul> <li>TFS Hackfest #3 (Castelldefels, 16-17 October 2023)</li> <li> <p>The link includes explanatory material on P4 for TeraFlowSDN, the set of guided walkthrough, and the details on the interactive sessions the participants addressed (and recordings), as well as a TFS Virtual Machine (Release 2.1).</p> </li> <li> <p>TFS Hackfest #2 (Madrid, 20-21 June 2023)</p> </li> <li> <p>The link includes explanatory material on gNMI and ContainerLab for TeraFlowSDN, the set of challenges the participants addressed (and recordings), as well as a TFS Virtual Machine (Pre-Release 2.1).</p> </li> <li> <p>OFC SC472 (San Diego, 6 March 2023)</p> </li> <li> <p>The link includes a tutorial-style slide deck, as well as a TFS Virtual Machine (Release 2).</p> </li> <li> <p>TFS Hackfest #1 (Amsterdam, 20 October 2022)</p> </li> <li>The link includes a tutorial-style slide deck (and recordings), as well as a TFS Virtual Machine (Pre-Release 2).</li> </ul>"},{"location":"#versions","title":"Versions","text":"<p>New versions of TeraFlowSDN are periodically released. Each release is properly tagged and a branch is kept for its future bug fixing, if needed.</p> <ul> <li>The branch master, points always to the latest stable version of the TeraFlowSDN controller.</li> <li>The branches release/X.Y.Z, point to the code for the different release versions indicated in branch name.</li> <li>Code in these branches can be considered stable, and no new features are planned.</li> <li>In case of bugs, point releases increasing revision number (Z) might be created.</li> <li>The main development branch is named as develop.</li> <li>Use with care! Might not be stable.</li> <li>The latest developments and contributions are added to this branch for testing and validation before reaching a release. </li> </ul> <p>To choose the appropriate branch, follow the steps described in 1.3. Deploy TeraFlowSDN > Checkout the Appropriate Git Branch</p>"},{"location":"#events","title":"Events","text":"<p>Find here after the list of past and future TFS Events:</p> <ul> <li>ETSI TeraFlowSDN Events </li> </ul>"},{"location":"#contact","title":"Contact","text":"<p>If your environment does not fit with the proposed assumptions and you experience issues preparing it to work with the ETSI TeraFlowSDN controller, contact the ETSI TeraFlowSDN SDG team through Slack</p>"},{"location":"FAQ/","title":"Frequently Asked Questions (FAQ)","text":""},{"location":"FAQ/#does-the-user-have-to-develop-the-3-elements-of-the-provider-aef-amf-and-apf","title":"Does the user have to develop the 3 elements of the provider (AEF, AMF and APF)?","text":"<p>No, you only have to make the request to the \"/onboarding\" endpoint. In it you must specify a CSR for the AEF, APF and AMF and you will receive the certificates for each of them in the response.</p>"},{"location":"FAQ/#there-is-one-party-that-publishes-the-api-and-another-that-exposes-it-what-is-the-difference","title":"There is one party that publishes the API and another that exposes it, what is the difference?","text":"<p>There are different services, the APF, intended for publishing the APIs, and the AEF, intended so that the invoker can call it. The APF is what connects to the Capif Core Function to publish the service and when the service is up, you need the AEF service so that invokers can connect to it.</p>"},{"location":"FAQ/#before-publishing-an-api-do-you-have-to-be-registered-in-capif","title":"Before publishing an API, do you have to be registered in CAPIF?","text":"<p>Yes, before publishing an API you must register using the POST /register endpoint.</p>"},{"location":"FAQ/#where-is-the-registration-done","title":"Where is the registration done?","text":"<p>Registration is done in a REST API outside of the CAPIF specification taht we have implemented.</p>"},{"location":"FAQ/#is-the-username-and-password-chosen-by-the-user-when-registering-or-is-it-assigned-when-requesting-registration-to-capif-public-instance","title":"Is the username and password chosen by the user when registering or is it assigned when requesting registration to CAPIF public instance?","text":"<p>When you make the request to the \"/endpoint\" of register, you will be returned a username and a password determined by CAPIF.</p>"},{"location":"FAQ/#what-is-a-csr","title":"What is a CSR?","text":"<p>A CSR is a Certificate Signing Request. It is a generated data block where the certificate is planned to be installed and contains key information such as public key, organization, and location, and is used to request a certificate from a certificate authority (CA). In CAPIF, 3 CSRs are necessary to register a provider, for AEF, APF and AMF.</p>"},{"location":"FAQ/#when-doing-the-register_provider-where-can-i-find-the-csrs-that-are-generated","title":"When doing the register_provider where can I find the CSRs that are generated?","text":"<p>When using the \"register_provider\" command, if you add the \"debug\" option, it shows you a json with the data used to register the provider. There we can find in the body a list of 3 elements corresponding to AEF, APF and AMF. IN each of them, the apiProbPubKey field corresponds to the CSR.</p>"},{"location":"FAQ/#how-to-use-the-example-client-capif_invoker_gui","title":"How to use the example client (CAPIF_INVOKER_GUI)?","text":"<p>First you have to make a \"./run.sh host:port\" indicating the address of the public CAPIF. Once the Docker containers are up, you have to do a \"./terminal_to_py_netapp.sh\" and then a \"python main.py\". At this point we will find ourselves in a console with some predefined commands to use the Client. If we press tab twice it will bring up the list of available commands.</p>"},{"location":"FAQ/#where-is-the-capif-public-instance-located","title":"Where is the CAPIF public instance located?","text":"<p>The CAPIF public instance can be found at the following URLs: - capif.mobilesandbox.cloud:37211 (HTTPS) - capif.mobilesandbox.cloud:37212 (HTTP)</p>"},{"location":"FAQ/#do-you-have-to-publish-3-apis-one-for-each-instance","title":"Do you have to publish 3 APIs? one for each instance?","text":"<p>No, you only have to publish a single API but each component is responsible for a specific service, whether publishing or exposing.</p>"},{"location":"FAQ/#once-the-api-is-published-is-it-always-active-or-do-you-have-to-republish-it-every-time-you-want-to-use-it","title":"Once the API is published, is it always active? Or do you have to republish it every time you want to use it?","text":"<p>It is better to unsubscribe the API every time you exit the application since otherwise it could be republished and it would be double.</p>"},{"location":"FAQ/#would-the-same-username-and-password-be-valid-for-different-invokers","title":"Would the same username and password be valid for different invokers?","text":"<p>Yes, a user can have multiple invokers at the same time, and as such, the username and password would be the same.</p>"},{"location":"FAQ/#what-is-the-notfication-destination-field-in-the-register_invoker-request","title":"What is the notfication destination field in the register_invoker request?","text":"<p>This is the callback URL used to notify events. CAPIF has an Event service to subscribe to that notifies actions such as a subscription to an API, a change in the state of an API...</p>"},{"location":"FAQ/#is-the-notification_destination-a-required-field-in-the-register_invoker","title":"Is the notification_destination a required field in the register_invoker","text":"<p>No, it is not mandatory, but if you do not enter it you will not receive any CAPIF events. For example, the APF may delete the API, you will not be notified that the API is no longer available.</p>"},{"location":"FAQ/#what-is-the-purpose-of-the-discover_service-function-in-the-invoker-client","title":"What is the purpose of the \"discover_service\" function in the invoker client?","text":"<p>The discover_service returns a json with all the services that exist exposed in CAPIF at that moment.</p>"},{"location":"FAQ/#what-is-the-purpose-of-the-get_security_auth-function-in-the-invoker-client","title":"What is the purpose of the \"get_security_auth\" function in the invoker client?","text":"<p>Sirve para pedir el token o para refrescarlo en caso de que haya caducado. You have to use that token to call the API from the invoker.</p>"},{"location":"FAQ/#what-is-the-purpose-of-the-register_security_context-function-in-the-invoker-client","title":"What is the purpose of the \"register_security_context\" function in the invoker client?","text":"<p>To consume the API it is necessary to have a Security Context registered with the data and the authentication method.</p>"},{"location":"FAQ/#is-a-user-the-same-as-an-exposer","title":"Is a user the same as an exposer?","text":"<p>No, a user registers in CAPIF and once done can have the role of invoker, provider or both.</p>"},{"location":"FAQ/#where-can-i-put-my-endpoint","title":"Where can I put my endpoint?","text":"<p>You have to set your endpoint when doing the \"publish_service\" functionality: <code>publish_service capif_ops/config_files/service_api_description_hello.json</code></p> <p>In the file \"service_api_description_hello.json\" you configure the service that is going to be exposed and by developing one to suit you, you expose your API.</p>"},{"location":"architecture/","title":"Architecture","text":""},{"location":"architecture/#architecture","title":"Architecture","text":"<p>The CAPIF architecture has three main components, Register Service, Vault and CCF, which are represented in the following image:</p> <p></p> <p>Each component is separated into different namespaces and all communications between them use Rest APIs.</p> <p>Apart from the communication between components, there are 2 other entities that can use them:</p> <ul> <li>Admin/superadmin: Responsible for managing users with the Register or carrying out special operations in the CCF.</li> <li>Users: They are those who want to use CAPIF, registering as a user in the Register and as Invoker or Provider in the CCF.</li> </ul>"},{"location":"architecture/#register-ns","title":"Register NS","text":"<p>This namespace belongs to the Register service, and we find 2 components:</p> <ul> <li>Register Service: It is responsible for managing all users who use CAPIF, in addition to providing the necessary information for its use.</li> <li>Register MONGO DATABASE: It is the Register database, in it we store all the information about registered users.</li> </ul>"},{"location":"architecture/#vault-ns","title":"Vault NS","text":"<p>This namespace belongs to Vault. </p> <p>This component is responsible for managing all CAPIF certificates, so other components such as the Register or the CCF communicate with it to create new certificates or request keys.</p>"},{"location":"architecture/#mon-ns","title":"Mon NS","text":"<p>This is the main namespace of CAPIF, since it contains all the CCF services in addition to other components:</p> <ul> <li>NGINX: Responsible for acting as a reverse proxy to distribute CAPIF requests to the different services, controlling whether or not they are authorized to access them.</li> <li>REDIS: Used for internal communication of services.</li> <li>CAPIF MONGO DATABASE: CAPIF database, where all information related to CAPIF services such as invokers, registered providers or published services is stored.</li> <li>HELPER: Service that simplifies integration with third parties such as external management portals.</li> </ul>"},{"location":"architecture/#new-architecture","title":"New Architecture","text":"<p>You can check the details of all these changes in the conversation on the OCF wiki.</p>"},{"location":"releasenotes/","title":"Releasenotes","text":""},{"location":"releasenotes/#release-100","title":"Release 1.0.0","text":""},{"location":"releasenotes/#new-features","title":"New Features","text":""},{"location":"releasenotes/#registration-flow-improved","title":"Registration Flow improved","text":"<ul> <li>Eliminated access from CAPIF to the Register user database when onboarding is performed.</li> <li>Isolation between CCF and Register services, interaction now is only by HTTPS requested between Register, CCF and Vault.</li> <li>Eliminated the \"role\" in user creation.<ul> <li>Now a user can be an invoker or a provider at the same time</li> </ul> </li> <li>Administrator User:<ul> <li>New entity in charge of registering and managing users of the register service.</li> </ul> </li> <li>UUID to identify users.<ul> <li>When you create a user, a uuid is associated with it</li> <li>The uuid will be contained in the token requested by the user and will be used to relate invokers and providers with users.</li> </ul> </li> <li>Endpoints changed and created:<ul> <li>Administrator endpoints:<ul> <li>/createUser: /register endpoint changed to createUser. Used to register new users.</li> <li>/deleteUser: /remove endpoint changed to this. Used to delete users and all the entities they had created.</li> <li>/login: Allows administrator to log in to obtain the necessary tokens for their requests.</li> <li>/refresh: Retrieve new access token token.</li> <li>/getUsers: Returns the list with all registered users.</li> </ul> </li> <li>Customer User:<ul> <li>/getauth now also returns the urls needed to use CAPIF, used by customer.</li> </ul> </li> </ul> </li> <li> <p>Security improvements:</p> <ul> <li>/login uses basic auth with administrator credentials.</li> <li>/getauth uses basic auth with customer user credentials.</li> <li>Other requests use the administrator access token obtained from login.</li> </ul> </li> <li> <p>Current fields on user creation by administrator:</p> </li> </ul> <pre><code>required_fields = {\n \"username\": str,\n \"password\": str,\n \"enterprise\": str,\n \"country\": str,\n \"email\": str,\n \"purpose\": str\n}\n\noptional_fields = {\n \"phone_number\": str,\n \"company_web\": str,\n \"description\": str\n}\n</code></pre> <ul> <li>Test plan has been updated with the new register flow. Please check OCF Registration Flow</li> <li>Video with explanation and demonstration of new register flow New Registration Demo</li> </ul>"},{"location":"releasenotes/#new-opencapif-architecture","title":"New OpenCAPIF architecture","text":"<ul> <li>New arquitecture with separated namespaces for Vault, CCF and Register components. Communication between them now are only allowed by using REST APIs.</li> <li> <p>New helper service inside CCF, it will simplify integration with third parties like external management portals.</p> </li> <li> <p>Helper endpoints:</p> <ul> <li>/getInvokers : Get the list of invokers from CAPIF</li> <li>/getProviders: Get the list of providers from CAPIF</li> <li>/getServices : Get the list of services published in CAPIF</li> <li>/getSecurityContext : Get the list of security contexts from CAPIF</li> <li>/getEvents : Get the list of events subscriptions from CAPIF</li> <li>/deleteEntities: Removes all entities registered by a user from the register</li> </ul> </li> <li> <p>Security in the helper</p> <ul> <li>To make requests to the helper you will need a superadmin certificate and password.</li> </ul> </li> </ul>"},{"location":"releasenotes/#events-api-upgrade","title":"Events API Upgrade","text":"<ul> <li>The event management at CCF is improved, EventNotification include Event Details with required information.</li> <li>Events updated:<ul> <li>SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE with apiIds</li> <li>SERVICE_API_UPDATE with serviceAPIDescriptions</li> <li>API_INVOKER_ONBOARDED, API_INVOKER_UPDATED, API_INVOKER_OFFBOARDED with apiInvokerIds.</li> </ul> </li> <li>Events Included:<ul> <li>SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE with invocationLogs</li> </ul> </li> <li>Test plan include 7 new tests in order to check new events implemented and scenarios of each notification implemented, with a complete check of Event Notification.</li> <li>Test plan documentation includes the new event tests OCF Event test plan documentation.</li> </ul>"},{"location":"releasenotes/#inital-implementation-of-cicd","title":"Inital implementation of CI/CD","text":"<ul> <li>The inital implementation of CI/CD on gitlab was performed.</li> <li>Detailed information in the CICD Wiki.</li> <li>Implement initial CI/CD:<ul> <li>Description of the CI process.<ul> <li>In CI phase, created design, jobs and security checks when a branch is pushed.</li> <li>The CI has jobs as:<ul> <li>Linting code, unit test (if needed),</li> <li>Build and push artifacts (images) in Git OCI register</li> <li>Security checks,</li> <li>SCA, CVS, SAST</li> <li>The vulnerabilities are exposed in Merge Request panel to be solved.</li> </ul> </li> </ul> </li> <li>Description of the CD process:<ul> <li>Defined the environments to OCF.<ul> <li>Production env.</li> <li>Pre-production env.</li> <li>Validation env.</li> <li>Dev-1, dev-2\u2026 envs (ephemeral)</li> </ul> </li> <li>Defined the naming convention to OCF releases<ul> <li>Tag in prod: v0.0.1-release</li> <li>Tag non-prod: v0.0.1-rc</li> <li>Other tags: v0.0.1-test, v0.0.1-smt</li> </ul> </li> <li>Defined the jobs of CD<ul> <li>CD ensures the deployment in multiple envs. Therefore, the CD pipeline has deploy-ocf, delete-ocf (if needed) jobs</li> </ul> </li> </ul> </li> <li>ETSI HIVE Labs:<ul> <li>Designed, created and the Kuberntes OCF cluster is running to support OCFs deployments.</li> <li>Iterating with ETSI HIVE\u2019s support to solve computing issues.<ul> <li>CPU compatibilities with OCF services (MongoDB): Fixed</li> </ul> </li> </ul> </li> </ul> </li> </ul>"},{"location":"releasenotes/#documentation","title":"Documentation","text":""},{"location":"releasenotes/#improvements-on-documentation","title":"Improvements on documentation","text":"<ul> <li>Documentation stored in OCF Documentation Repository</li> <li>Continuous Integration included at repository for web documentation:<ul> <li>Develop version of documentation is automatically generated on each merge to develop branch.</li> <li>Tagged version from main create documentation with related tag as version.</li> </ul> </li> </ul>"},{"location":"releasenotes/#technical-debt-solved","title":"Technical Debt Solved","text":""},{"location":"releasenotes/#improved-testing-with-robot-in-order-to-cover","title":"Improved Testing with Robot in order to cover","text":"<ul> <li>Support of new Register flows.</li> <li>Allow different URLs for register, ccf and vault services.</li> <li>New Variables included to manage new architecture under test.</li> <li>Mock server developed to add the functionality of write tests involving notification from Service Under Test.</li> <li>Docker image improved generation and libraries upgraded to Robot Framework 7.</li> </ul>"},{"location":"releasenotes/#improved-security-on-db","title":"Improved security on DB","text":"<ul> <li>Credentials requested to access mongo databases.</li> <li>Credentials requested also by mongo-express.</li> </ul>"},{"location":"releasenotes/#scripts-upgraded","title":"Scripts upgraded","text":"<ul> <li>Docker compose version 2 used on them.</li> <li>New cleaning script developed.</li> <li>Scripts upgraded:<ul> <li>check_services_are_running.sh: Checks if all essential services (Vault, CCF and Register) are running.</li> <li>clean_capif_docker_services.sh: Shutdowns and removes all services essential services.</li> <li>clean_capif_temporary_files.sh: Removes temporaly files from local repository. </li> <li>run.sh: Launch Essential services locally using docker compose, also monitoring can be launched.</li> <li>run_capif_tests.sh: Launch Robot Framwork Tests.</li> <li>show_logs.sh: Show locally logs of Services running.</li> <li>run_mock_server.sh: Launch mock server locally on all interfaces. This axiliary server is only used by tagged mockserver tests on Robot Framework.</li> <li>clean_mock_server.sh: Remove mock server local deployment.</li> <li>deploy.sh: This script simplify the way to download capif repository.</li> </ul> </li> </ul>"},{"location":"releasenotes/#codebase-improvements","title":"Codebase Improvements","text":"<ul> <li>Documentation is now on splitted repository OCF Documentation Repository</li> <li>Test plan was moved to OCF Documentation Repository</li> <li>Obsolote data is removed.</li> <li>Repository Reorganization: Enhanced structure and maintainability with a better directory layout and clearer module separation.</li> <li>Code Quality Enhancements: Refactored code and fixed known issues</li> </ul>"},{"location":"releasenotes/#migration-to-gunicorn","title":"Migration to GUNICORN","text":"<ul> <li>Include production server on each microservice: Release 0 use Flask developer server, now we use GUNICORN.</li> </ul>"},{"location":"releasenotes/#release-00","title":"Release 0.0","text":"<p>The APIs included in Release 0.0 are:</p> <ul> <li>JWT Authentication APIs</li> <li>CAPIF Invoker Management API</li> <li>CAPIF Publish API</li> <li>CAPIF Discover API</li> <li>CAPIF Security API</li> <li>CAPIF Events API</li> <li>CAPIF Provider Management API</li> </ul> <p>This Release also includes a Robot Test Suite for all those services and a Postman Test Suite for simple testing.</p>"},{"location":"deployment_guide/deployment_guide/","title":"1. Deployment Guide","text":"<p>This section walks you through the process of deploying TeraFlowSDN on top of a machine running MicroK8s Kubernetes platform. The guide includes the details on configuring and installing the machine, installing and configuring MicroK8s, and deploying and reporting the status of the TeraFlowSDN controller.</p>"},{"location":"deployment_guide/deployment_guide/#11-configure-your-machine","title":"1.1. Configure your Machine","text":"<p>In this section, we describe how to configure a machine (physical or virtual) to be used as the deployment, execution, and development environment for the ETSI TeraFlowSDN controller. Choose your preferred environment below and follow the instructions provided.</p> <p>NOTE: If you already have a remote physical server fitting the requirements specified in this section feel free to use it instead of deploying a local VM. Check 1.1.1. Physical Server for further details.</p> <p>Virtualization platforms tested are:</p> <ul> <li>Physical Server</li> <li>Oracle Virtual Box</li> <li>VMWare Fusion</li> <li>OpenStack</li> <li>Vagrant Box</li> </ul>"},{"location":"deployment_guide/deployment_guide/#111-physical-server","title":"1.1.1. Physical ServerServer SpecificationsClusterized DeploymentNetworkingOperating SystemUpgrade the Ubuntu distribution","text":"<p>This section describes how to configure a physical server for running ETSI TeraFlowSDN(TFS) controller.</p> <p>Minimum Server Specifications for development and basic deployment</p> <ul> <li>CPU: 4 cores</li> <li>RAM: 8 GB</li> <li>Disk: 60 GB</li> <li>1 GbE NIC</li> </ul> <p>Recommended Server Specifications for development and basic deployment</p> <ul> <li>CPU: 6 cores</li> <li>RAM: 12 GB</li> <li>Disk: 80 GB</li> <li>1 GbE NIC</li> </ul> <p>Server Specifications for best development and deployment experience</p> <ul> <li>CPU: 8 cores</li> <li>RAM: 32 GB</li> <li>Disk: 120 GB</li> <li>1 GbE NIC</li> </ul> <p>NOTE: the specifications listed above are provided as a reference. They depend also on the CPU clock frequency, the RAM memory, the disk technology and speed, etc.</p> <p>For development purposes, it is recommended to run the VSCode IDE (or the IDE of your choice) in a more powerful server, for instance, the recommended server specifications for development and basic deployment.</p> <p>Given that TeraFlowSDN follows a micro-services architecture, for the deployment, it might be better to use many clusterized servers with many slower cores than a single server with few highly performant cores.</p> <p>You might consider creating a cluster of machines each featuring, at least, the minimum server specifications. That solution brings you scalability in the future.</p> <p>No explicit indications are given in terms of networking besides that servers need access to the Internet for downloading dependencies, binaries, and packages while building and deploying the TeraFlowSDN components.</p> <p>Besides that, the network requirements are essentially the same than that required for running a classical Kubernetes environment. To facilitate the deployment, we extensively use MicroK8s, thus the network requirements are, essentially, the same demanded by MicroK8s, especially, if you consider creating a Kubernetes cluster.</p> <p>As a reference, the other deployment solutions based on VMs assume the VM is connected to a virtual network configured with the IP range <code>10.0.2.0/24</code> and have the gateway at IP <code>10.0.2.1</code>. The VMs have the IP address <code>10.0.2.10</code>.</p> <p>The minimum required ports to be accessible are: - 22/SSH : for management purposes - 80/HTTP : for the TeraFlowSDN WebUI and Grafana dashboard - 8081/HTTPS : for the CockroachDB WebUI</p> <p>Other ports might be required if you consider to deploy addons such as Kubernetes observability, etc. The details on these ports are left appart given they might vary depending on the Kubernetes environment you use.</p> <p>The recommended Operating System for deploying TeraFlowSDN is Ubuntu Server 22.04 LTS or Ubuntu Server 20.04 LTS. Other version might work, but we have not tested them. We strongly recommend using Long Term Support (LTS) versions as they provide better stability.</p> <p>Below we provide some installation guidelines: - Installation Language: English - Autodetect your keyboard - If asked, select \"Ubuntu Server\" (do not select \"Ubuntu Server (minimized)\"). - Configure static network specifications (adapt them based on your particular setup):</p> Interface IPv4 Method Subnet Address Gateway Name servers Search domains enp0s3 Manual 10.0.2.0/24 10.0.2.10 10.0.2.1 8.8.8.8,8.8.4.4 <ul> <li>Leave proxy and mirror addresses as they are</li> <li>Let the installer self-upgrade (if asked).</li> <li>Use an entire disk for the installation</li> <li>Disable setup of the disk as LVM group</li> <li>Double check that NO swap space is allocated in the partition table. Kubernetes does not work properly with SWAP.</li> <li>Configure your user and system names:</li> <li>User name: <code>TeraFlowSDN</code></li> <li>Server's name: <code>tfs-vm</code></li> <li>Username: <code>tfs</code></li> <li>Password: <code>tfs123</code></li> <li>Install Open SSH Server</li> <li>Import SSH keys, if any.</li> <li>Featured Server Snaps</li> <li>Do not install featured server snaps. It will be done manually later to illustrate how to uninstall and reinstall them in case of trouble with.</li> <li>Let the system install and upgrade the packages.</li> <li>This operation might take some minutes depending on how old is the Optical Drive ISO image you use and your Internet connection speed.</li> <li>Restart the VM when the installation is completed.</li> </ul> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul>"},{"location":"deployment_guide/deployment_guide/#112-oracle-virtual-box","title":"1.1.2. Oracle Virtual BoxCreate a NAT Network in VirtualBoxCreate VM in VirtualBox:Install Ubuntu 22.04 LTS Operating System","text":"<p>This section describes how to configure a VM for running ETSI TeraFlowSDN(TFS) controller using Oracle VirtualBox. It has been tested with VirtualBox up to version 6.1.40 r154048.</p> <p>In \"Oracle VM VirtualBox Manager\", Menu \"File > Preferences... > Network\", create a NAT network with the following specifications:</p> Name CIDR DHCP IPv6 TFS-NAT-Net 10.0.2.0/24 Disabled Disabled <p>Within the newly created \"TFS-NAT-Net\" NAT network, configure the following IPv4 forwarding rules:</p> Name Protocol Host IP Host Port Guest IP Guest Port SSH TCP 127.0.0.1 2200 10.0.2.10 22 HTTP TCP 127.0.0.1 8080 10.0.2.10 80 <p>Note: IP address 10.0.2.10 is the one that will be assigned to the VM.</p> <ul> <li>Name: TFS-VM</li> <li>Type/Version: Linux / Ubuntu (64-bit)</li> <li>CPU (*): 4 vCPUs @ 100% execution capacity</li> <li>RAM: 8 GB</li> <li>Disk: 60 GB, Virtual Disk Image (VDI), Dynamically allocated</li> <li>Optical Drive ISO Image: \"ubuntu-22.04.X-live-server-amd64.iso\"</li> <li>Download the latest Long Term Support (LTS) version of the Ubuntu Server image from Ubuntu 22.04 LTS, e.g., \"ubuntu-22.04.X-live-server-amd64.iso\".</li> <li>Note: use Ubuntu Server image instead of Ubuntu Desktop to create a lightweight VM.</li> <li>Network Adapter 1 (*): enabled, attached to NAT Network \"TFS-NAT-Net\"</li> <li>Minor adjustments (*):</li> <li>Audio: disabled</li> <li>Boot order: disable \"Floppy\"</li> </ul> <p>Note: (*) settings to be editing after the VM is created.</p> <p>In \"Oracle VM VirtualBox Manager\", start the VM in normal mode, and follow the installation procedure. Below we provide some installation guidelines: - Installation Language: English - Autodetect your keyboard - If asked, select \"Ubuntu Server\" (do not select \"Ubuntu Server (minimized)\"). - Configure static network specifications:</p> Interface IPv4 Method Subnet Address Gateway Name servers Search domains enp0s3 Manual 10.0.2.0/24 10.0.2.10 10.0.2.1 8.8.8.8,8.8.4.4 <ul> <li>Leave proxy and mirror addresses as they are</li> <li>Let the installer self-upgrade (if asked).</li> <li>Use an entire disk for the installation</li> <li>Disable setup of the disk as LVM group</li> <li>Double check that NO swap space is allocated in the partition table. Kubernetes does not work properly with SWAP.</li> <li>Configure your user and system names:</li> <li>User name: TeraFlowSDN</li> <li>Server's name: tfs-vm</li> <li>Username: tfs</li> <li>Password: tfs123</li> <li>Install Open SSH Server</li> <li>Import SSH keys, if any.</li> <li>Featured Server Snaps</li> <li>Do not install featured server snaps. It will be done manually later to illustrate how to uninstall and reinstall them in case of trouble with.</li> <li>Let the system install and upgrade the packages.</li> <li>This operation might take some minutes depending on how old is the Optical Drive ISO image you use and your Internet connection speed.</li> <li>Restart the VM when the installation is completed.</li> </ul> <p>Upgrade the Ubuntu distribution</p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul> <p>Install VirtualBox Guest Additions On VirtualBox Manager, open the VM main screen. If you are running the VM in headless mode, right click over the VM in the VirtualBox Manager window and click \"Show\". If a dialog informing about how to leave the interface of the VM is shown, confirm pressing \"Switch\" button. The interface of the VM should appear.</p> <p>Click menu \"Device > Insert Guest Additions CD image...\"</p> <p>On the VM terminal, type:</p> <pre><code>sudo apt-get install -y linux-headers-$(uname -r) build-essential dkms\n # This command might take some minutes depending on your VM specs and your Internet access speed.\nsudo mount /dev/cdrom /mnt/\ncd /mnt/\nsudo ./VBoxLinuxAdditions.run\n # This command might take some minutes depending on your VM specs.\nsudo reboot\n</code></pre>"},{"location":"deployment_guide/deployment_guide/#113-vmware-fusion","title":"1.1.3. VMWare FusionCreate VM in VMWare Fusion:Install Ubuntu 22.04.1 LTS Operating SystemUpgrade the Ubuntu distribution","text":"<p>This section describes how to configure a VM for running ETSI TeraFlowSDN(TFS) controller using VMWare Fusion. It has been tested with VMWare Fusion version 12 and 13.</p> <p>In \"VMWare Fusion\" manager, create a new network from the \"Settings/Network\" menu.</p> <ul> <li>Unlock to make changes</li> <li>Press the + icon and create a new network</li> <li>Change the name to TFS-NAT-Net</li> <li>Check \"Allow virtual machines on this network to connect to external network (NAT)\"</li> <li>Do not check \"Enable IPv6\"</li> <li>Add port forwarding for HTTP and SSH</li> <li>Uncheck \"Provide address on this network via DHCP\"</li> </ul> <p>Create a new VM an Ubuntu 22.04.1 ISO:</p> <ul> <li>Display Name: TeraFlowSDN</li> <li>Username: tfs</li> <li>Password: tfs123</li> </ul> <p>On the next screen press \"Customize Settings\", save the VM and in \"Settings\" change: - Change to use 4 CPUs - Change to access 8 GB of RAM - Change disk to size 60 GB - Change the network interface to use the previously created TFS-NAT-Net</p> <p>Run the VM to start the installation.</p> <p>The installation will be automatic, without any configuration required.</p> <ul> <li>Configure the guest IP, gateway and DNS:</li> </ul> <p>Using the Network Settings for the wired connection, set the IP to 10.0.2.10, the mask to 255.255.255.0, the gateway to 10.0.2.2 and the DNS to 10.0.2.2.</p> <ul> <li>Disable and remove swap file:</li> </ul> <p>$ sudo swapoff -a $ sudo rm /swapfile</p> <p>Then you can remove or comment the /swapfile entry in /etc/fstab</p> <ul> <li>Install Open SSH Server</li> <li> <p>Import SSH keys, if any.</p> </li> <li> <p>Restart the VM when the installation is completed.</p> </li> </ul> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre>"},{"location":"deployment_guide/deployment_guide/#114-openstack","title":"1.1.4. OpenStackCreate a Security Group in OpenStack <p> In OpenStack, go to Project - Network - Security Groups - Create Security Group with name TFS</p> <p>Add the following rules:</p> Direction Ether Type IP Protocol Port Range Remote IP Prefix Ingress IPv4 TCP 22 (SSH) 0.0.0.0/0 Ingress IPv4 TCP 2200 0.0.0.0/0 Ingress IPv4 TCP 8080 0.0.0.0/0 Ingress IPv4 TCP 80 0.0.0.0/0 Egress IPv4 Any Any 0.0.0.0/0 Egress IPv6 Any Any ::/0 <p>Note: The IP address will be assigned depending on the network you have configured inside OpenStack. This IP will have to be modified in TeraFlow configuration files which by default use IP 10.0.2.10</p> Create a flavour <p></p> <p>From dashboard (Horizon)</p> <p>Go to Admin - Compute - Flavors and press Create Flavor</p> <ul> <li>Name: TFS</li> <li>VCPUs: 4</li> <li>RAM (MB): 8192</li> <li>Root Disk (GB): 60</li> </ul> <p>From CLI</p> <pre><code> openstack flavor create TFS --id auto --ram 8192 --disk 60 --vcpus 8\n</code></pre> Create an instance in OpenStack: <p></p> <ul> <li>Instance name: TFS-VM</li> <li>Origin: [Ubuntu-22.04 cloud image] (https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img)</li> <li>Create new volume: No</li> <li>Flavor: TFS</li> <li>Networks: extnet </li> <li>Security Groups: TFS</li> <li>Configuration: Include the following cloud-config</li> </ul> <pre><code>#cloud-config\n# Modifies the password for the VM instance\nusername: ubuntu\npassword: <your-password>\nchpasswd: { expire: False }\nssh_pwauth: True\n</code></pre> Upgrade the Ubuntu distribution <p></p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul>","text":"<p>This section describes how to configure a VM for running ETSI TeraFlowSDN(TFS) controller using OpenStack. It has been tested with OpenStack Kolla up to Yoga version. </p>"},{"location":"deployment_guide/deployment_guide/#115-vagrant-box","title":"1.1.5. Vagrant Box <p>","text":""},{"location":"deployment_guide/deployment_guide/#12-install-microk8s","title":"1.2. Install MicroK8s","text":"<p>This section describes how to deploy the MicroK8s Kubernetes platform and configure it to be used with ETSI TeraFlowSDN controller. Besides, Docker is installed to build docker images for the ETSI TeraFlowSDN controller.</p> <p>The steps described in this section might take some minutes depending on your internet connection speed and the resources assigned to your VM, or the specifications of your physical server.</p> <p>To facilitate work, these steps are easier to be executed through an SSH connection, for instance using tools like PuTTY or MobaXterm.</p> Upgrade the Ubuntu distribution <p> Skip this step if you already did it during the creation of the VM.</p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> Install prerequisites <p></p> <pre><code>sudo apt-get install -y ca-certificates curl gnupg lsb-release snapd jq\n</code></pre> Install Docker CE <p> Install Docker CE and Docker BuildX plugin</p> <pre><code>sudo apt-get install -y docker.io docker-buildx\n</code></pre> <p>NOTE: Starting from Docker v23, Build architecture has been updated and <code>docker build</code> command entered into deprecation process in favor of the new <code>docker buildx build</code> command. Package <code>docker-buildx</code> provides the new <code>docker buildx build</code> command.</p> <p>Add key \"insecure-registries\" with the private repository to the daemon configuration. It is done in two commands since sometimes read from and write to same file might cause trouble.</p> <pre><code>if [ -s /etc/docker/daemon.json ]; then cat /etc/docker/daemon.json; else echo '{}'; fi \\\n | jq 'if has(\"insecure-registries\") then . else .+ {\"insecure-registries\": []} end' -- \\\n | jq '.\"insecure-registries\" |= (.+ [\"localhost:32000\"] | unique)' -- \\\n | tee tmp.daemon.json\nsudo mv tmp.daemon.json /etc/docker/daemon.json\nsudo chown root:root /etc/docker/daemon.json\nsudo chmod 600 /etc/docker/daemon.json\n</code></pre> <p>Restart the Docker daemon</p> <pre><code>sudo systemctl restart docker\n</code></pre> Install MicroK8s <p></p> <p>Important: Some TeraFlowSDN dependencies need to be executed on top of MicroK8s/Kubernetes v1.24. It is not guaranteed (by now) to run on newer versions.</p> <pre><code># Install MicroK8s\nsudo snap install microk8s --classic --channel=1.24/stable\n\n# Create alias for command \"microk8s.kubectl\" to be usable as \"kubectl\"\nsudo snap alias microk8s.kubectl kubectl\n</code></pre> <p>It is important to make sure that <code>ufw</code> will not interfere with the internal pod-to-pod and pod-to-Internet traffic. To do so, first check the status. If <code>ufw</code> is active, use the following command to enable the communication.</p> <pre><code>\n# Verify status of ufw firewall\nsudo ufw status\n\n# If ufw is active, install following rules to enable access pod-to-pod and pod-to-internet\nsudo ufw allow in on cni0 && sudo ufw allow out on cni0\nsudo ufw default allow routed\n</code></pre> <p>NOTE: MicroK8s can be used to compose a Highly Available Kubernetes cluster enabling you to construct an environment combining the CPU, RAM and storage resources of multiple machines. If you are interested in this procedure, review the official instructions in How to build a highly available Kubernetes cluster with MicroK8s, in particular, the step Create a MicroK8s multi-node cluster.</p> <p>References:</p> <ul> <li>The lightweight Kubernetes > Install MicroK8s</li> <li>Install a local Kubernetes with MicroK8s</li> <li>How to build a highly available Kubernetes cluster with MicroK8s</li> </ul> Add user to the docker and microk8s groups <p></p> <p>It is important that your user has the permission to run <code>docker</code> and <code>microk8s</code> in the terminal. To allow this, you need to add your user to the <code>docker</code> and <code>microk8s</code> groups with the following commands:</p> <pre><code>sudo usermod -a -G docker $USER\nsudo usermod -a -G microk8s $USER\nsudo chown -f -R $USER $HOME/.kube\nsudo reboot\n</code></pre> <p>In case that you get trouble executing the following commands, might due to the .kube folder is not automatically provisioned into your home folder, you may follow the steps below:</p> <pre><code>mkdir -p $HOME/.kube\nsudo chown -f -R $USER $HOME/.kube\nmicrok8s config > $HOME/.kube/config\nsudo reboot\n</code></pre> Check status of Kubernetes and addons <p> To retrieve the status of Kubernetes once, run the following command:</p> <pre><code>microk8s.status --wait-ready\n</code></pre> <p>To retrieve the status of Kubernetes periodically (e.g., every 1 second), run the following command:</p> <pre><code>watch -n 1 microk8s.status --wait-ready\n</code></pre> Check all resources in Kubernetes <p> To retrieve the status of the Kubernetes resources once, run the following command:</p> <pre><code>kubectl get all --all-namespaces\n</code></pre> <p>To retrieve the status of the Kubernetes resources periodically (e.g., every 1 second), run the following command:</p> <pre><code>watch -n 1 kubectl get all --all-namespaces\n</code></pre> Enable addons <p></p> <p>First, we need to enable the community plugins (maintained by third parties):</p> <pre><code>microk8s.enable community\n</code></pre> <p>The Addons to be enabled are:</p> <ul> <li><code>dns</code>: enables resolving the pods and services by name</li> <li><code>helm3</code>: required to install NATS</li> <li><code>hostpath-storage</code>: enables providing storage for the pods (required by <code>registry</code>)</li> <li><code>ingress</code>: deploys an ingress controller to expose the microservices outside Kubernetes</li> <li><code>registry</code>: deploys a private registry for the TFS controller images</li> <li><code>linkerd</code>: deploys the linkerd service mesh used for load balancing among replicas</li> <li><code>prometheus</code>: set of tools that enable TFS observability through per-component instrumentation</li> <li><code>metrics-server</code>: deploys the Kubernetes metrics server for API access to service metrics</li> </ul> <pre><code>microk8s.enable dns helm3 hostpath-storage ingress registry prometheus metrics-server linkerd\n</code></pre> <p>Important: Enabling some of the addons might take few minutes. Do not proceed with next steps until the addons are ready. Otherwise, the deployment might fail. To confirm everything is up and running:</p> <ol> <li>Periodically Check the status of Kubernetes until you see the addons [dns, ha-cluster, helm3, hostpath-storage, ingress, linkerd, metrics-server, prometheus, registry, storage] in the enabled block.</li> <li>Periodically Check Kubernetes resources until all pods are Ready and Running.</li> <li>If it takes too long for the Pods to be ready, we observed that rebooting the machine may help.</li> </ol> <p>Then, create aliases to make the commands easier to access:</p> <pre><code>sudo snap alias microk8s.helm3 helm3\nsudo snap alias microk8s.linkerd linkerd\n</code></pre> <p>To validate that <code>linkerd</code> is working correctly, run:</p> <pre><code>linkerd check\n</code></pre> <p>To validate that the <code>metrics-server</code> is working correctly, run:</p> <pre><code>kubectl top pods --all-namespaces\n</code></pre> <p>and you should see a screen similar to the <code>top</code> command in Linux, showing the columns namespace, pod name, CPU (cores), and MEMORY (bytes).</p> <p>In case pods are not starting, check information from pods logs. For example, linkerd is sensitive for proper /etc/resolv.conf syntax.</p> <pre><code>kubectl logs <podname> --namespace <namespace>\n</code></pre> <p>If the command shows an error message, also restarting the machine might help.</p> Stop, Restart, and Redeploy <p> Find below some additional commands you might need while you work with MicroK8s:</p> <pre><code>microk8s.stop # stop MicroK8s cluster (for instance, before power off your computer)\nmicrok8s.start # start MicroK8s cluster\nmicrok8s.reset # reset infrastructure to a clean state\n</code></pre> <p>If the following commands does not work to recover the MicroK8s cluster, you can redeploy it.</p> <p>If you want to keep MicroK8s configuration, use:</p> <pre><code>sudo snap remove microk8s\n</code></pre> <p>If you need to completely drop MicroK8s and its complete configuration, use:</p> <pre><code>sudo snap remove microk8s --purge\nsudo apt-get remove --purge docker.io docker-buildx\n</code></pre> <p>IMPORTANT: After uninstalling MicroK8s, it is convenient to reboot the computer (the VM if you work on a VM, or the physical computer if you use a physical computer). Otherwise, there are system configurations that are not correctly cleaned. Especially in what port forwarding and firewall rules matters.</p> <p>After the reboot, redeploy as it is described in this section.</p>"},{"location":"deployment_guide/deployment_guide/#13-deploy-teraflowsdn","title":"1.3. Deploy TeraFlowSDN","text":"<p>This section describes how to deploy TeraFlowSDN controller on top of MicroK8s using the environment configured in the previous sections.</p> Install prerequisites <p></p> <pre><code>sudo apt-get install -y git curl jq\n</code></pre> Clone the Git repository of the TeraFlowSDN controller <p> Clone from ETSI-hosted GitLab code repository:</p> <pre><code>mkdir ~/tfs-ctrl\ngit clone https://labs.etsi.org/rep/tfs/controller.git ~/tfs-ctrl\n</code></pre> <p>Important: The original H2020-TeraFlow project hosted on GitLab.com has been archieved and will not receive further contributions/updates. Please, clone from ETSI-hosted GitLab code repository.</p> Checkout the appropriate Git branch <p> TeraFlowSDN controller versions can be found in the appropriate release tags and/or branches as described in Home > Versions.</p> <p>By default the branch master is checked out and points to the latest stable version of the TeraFlowSDN controller, while branch develop contains the latest developments and contributions under test and validation.</p> <p>To switch to the appropriate branch run the following command, changing <code>develop</code> by the name of the branch you want to deploy:</p> <pre><code>cd ~/tfs-ctrl\ngit checkout develop\n</code></pre> Prepare a deployment script with the deployment settings <p> Create a new deployment script, e.g., <code>my_deploy.sh</code>, adding the appropriate settings as follows. This section provides just an overview of the available settings. An example <code>my_deploy.sh</code> script is provided in the root folder of the project for your convenience with full description of all the settings.</p> <p>Note: The example <code>my_deploy.sh</code> script provides reasonable settings for deploying a functional and complete enough TeraFlowSDN controller, and a brief description of their meaning. To see extended descriptions, check scripts in the <code>deploy</code> folder.</p> <pre><code>cd ~/tfs-ctrl\ntee my_deploy.sh >/dev/null << EOF\n# ----- TeraFlowSDN ------------------------------------------------------------\nexport TFS_REGISTRY_IMAGES=\"http://localhost:32000/tfs/\"\nexport TFS_COMPONENTS=\"context device ztp monitoring pathcomp service slice nbi webui load_generator\"\nexport TFS_IMAGE_TAG=\"dev\"\nexport TFS_K8S_NAMESPACE=\"tfs\"\nexport TFS_EXTRA_MANIFESTS=\"manifests/nginx_ingress_http.yaml\"\nexport TFS_GRAFANA_PASSWORD=\"admin123+\"\nexport TFS_SKIP_BUILD=\"\"\n\n# ----- CockroachDB ------------------------------------------------------------\nexport CRDB_NAMESPACE=\"crdb\"\nexport CRDB_EXT_PORT_SQL=\"26257\"\nexport CRDB_EXT_PORT_HTTP=\"8081\"\nexport CRDB_USERNAME=\"tfs\"\nexport CRDB_PASSWORD=\"tfs123\"\nexport CRDB_DATABASE=\"tfs\"\nexport CRDB_DEPLOY_MODE=\"single\"\nexport CRDB_DROP_DATABASE_IF_EXISTS=\"YES\"\nexport CRDB_REDEPLOY=\"\"\n\n# ----- NATS -------------------------------------------------------------------\nexport NATS_NAMESPACE=\"nats\"\nexport NATS_EXT_PORT_CLIENT=\"4222\"\nexport NATS_EXT_PORT_HTTP=\"8222\"\nexport NATS_REDEPLOY=\"\"\n\n# ----- QuestDB ----------------------------------------------------------------\nexport QDB_NAMESPACE=\"qdb\"\nexport QDB_EXT_PORT_SQL=\"8812\"\nexport QDB_EXT_PORT_ILP=\"9009\"\nexport QDB_EXT_PORT_HTTP=\"9000\"\nexport QDB_USERNAME=\"admin\"\nexport QDB_PASSWORD=\"quest\"\nexport QDB_TABLE_MONITORING_KPIS=\"tfs_monitoring_kpis\"\nexport QDB_TABLE_SLICE_GROUPS=\"tfs_slice_groups\"\nexport QDB_DROP_TABLES_IF_EXIST=\"YES\"\nexport QDB_REDEPLOY=\"\"\n\nEOF\n</code></pre> <p>The settings are organized in 4 sections: - Section <code>TeraFlowSDN</code>: - <code>TFS_REGISTRY_IMAGE</code> enables to specify the private Docker registry to be used, by default, we assume to use the Docker respository enabled in MicroK8s. - <code>TFS_COMPONENTS</code> specifies the components their Docker image will be rebuilt, uploaded to the private Docker registry, and deployed in Kubernetes. - <code>TFS_IMAGE_TAG</code> defines the tag to be used for Docker images being rebuilt and uploaded to the private Docker registry. - <code>TFS_K8S_NAMESPACE</code> specifies the name of the Kubernetes namespace to be used for deploying the TFS components. - <code>TFS_EXTRA_MANIFESTS</code> enables to provide additional manifests to be applied into the Kubernetes environment during the deployment. Typical use case is to deploy ingress controllers, service monitors for Prometheus, etc. - <code>TFS_GRAFANA_PASSWORD</code> lets you specify the password you want to use for the <code>admin</code> user of the Grafana instance being deployed and linked to the Monitoring component. - <code>TFS_SKIP_BUILD</code>, if set to <code>YES</code>, prevents rebuilding the Docker images. That means, the deploy script will redeploy existing Docker images without rebuilding/updating them.</p> <ul> <li>Section <code>CockroachDB</code>: enables to configure the deployment of the backend CockroachDB database.</li> <li> <p>Check example script <code>my_deploy.sh</code> for further details.</p> </li> <li> <p>Section <code>NATS</code>: enables to configure the deployment of the backend NATS message broker.</p> </li> <li> <p>Check example script <code>my_deploy.sh</code> for further details.</p> </li> <li> <p>Section <code>QuestDB</code>: enables to configure the deployment of the backend QuestDB timeseries database.</p> </li> <li>Check example script <code>my_deploy.sh</code> for further details.</li> </ul> Confirm that MicroK8s is running <p></p> <p>Run the following command:</p> <pre><code>microk8s status\n</code></pre> <p>If it is reported <code>microk8s is not running, try microk8s start</code>, run the following command to start MicroK8s:</p> <pre><code>microk8s start\n</code></pre> <p>Confirm everything is up and running:</p> <ol> <li>Periodically Check the status of Kubernetes until you see the addons [dns, ha-cluster, helm3, hostpath-storage, ingress, registry, storage] in the enabled block.</li> <li>Periodically Check Kubernetes resources until all pods are Ready and Running.</li> </ol> Deploy TFS controller <p> First, source the deployment settings defined in the previous section. This way, you do not need to specify the environment variables in each and every command you execute to operate the TFS controller. Be aware to re-source the file if you open new terminal sessions. Then, run the following command to deploy TeraFlowSDN controller on top of the MicroK8s Kubernetes platform.</p> <pre><code>cd ~/tfs-ctrl\nsource my_deploy.sh\n./deploy/all.sh\n</code></pre> <p>The script performs the following steps:</p> <ul> <li>Executes script <code>./deploy/crdb.sh</code> to automate deployment of CockroachDB database used by Context component.</li> <li>The script automatically checks if CockroachDB is already deployed.</li> <li>If there are settings instructing to drop the database and/or redeploy CockroachDB, it does the appropriate actions to honor them as defined in previous section.</li> <li>Executes script <code>./deploy/nats.sh</code> to automate deployment of NATS message broker used by Context component.</li> <li>The script automatically checks if NATS is already deployed.</li> <li>If there are settings instructing to redeploy the message broker, it does the appropriate actions to honor them as defined in previous section.</li> <li>Executes script <code>./deploy/qdb.sh</code> to automate deployment of QuestDB timeseries database used by Monitoring component.</li> <li>The script automatically checks if QuestDB is already deployed.</li> <li>If there are settings instructing to redeploy the timeseries database, it does the appropriate actions to honor them as defined in previous section.</li> <li>Executes script <code>./deploy/tfs.sh</code> to automate deployment of TeraFlowSDN.</li> <li>Creates the namespace defined in <code>TFS_K8S_NAMESPACE</code></li> <li>Creates secrets for CockroachDB, NATS, and QuestDB to be used by Context and Monitoring components.</li> <li>Builds the Docker images for the components defined in <code>TFS_COMPONENTS</code></li> <li>Tags the Docker images with the value of <code>TFS_IMAGE_TAG</code></li> <li>Pushes the Docker images to the repository defined in <code>TFS_REGISTRY_IMAGE</code></li> <li>Deploys the components defined in <code>TFS_COMPONENTS</code></li> <li>Creates the file <code>tfs_runtime_env_vars.sh</code> with the environment variables for the components defined in <code>TFS_COMPONENTS</code> defining their local host addresses and their port numbers.</li> <li>Applies extra manifests defined in <code>TFS_EXTRA_MANIFESTS</code> such as:<ul> <li>Creating an ingress controller listening at port 80 for HTTP connections to enable external access to the TeraFlowSDN WebUI, Grafana Dashboards, and Compute NBI interfaces.</li> <li>Deploying service monitors to enable monitoring the performance of the components, device drivers and service handlers.</li> </ul> </li> <li>Initialize and configure the Grafana dashboards (if Monitoring component is deployed)</li> <li>Report a summary of the deployment</li> <li>See Show Deployment and Logs</li> </ul>"},{"location":"deployment_guide/deployment_guide/#14-webui-and-grafana-dashboards","title":"1.4. WebUI and Grafana Dashboards","text":"<p>This section describes how to get access to the TeraFlowSDN controller WebUI and the monitoring Grafana dashboards.</p> Access the TeraFlowSDN WebUI <p> If you followed the installation steps based on MicroK8s, you got an ingress controller installed that exposes on TCP port 80.</p> <p>Besides, the ingress controller defines the following reverse proxy paths (on your local machine):</p> <ul> <li><code>http://127.0.0.1/webui</code>: points to the WebUI of TeraFlowSDN.</li> <li><code>http://127.0.0.1/grafana</code>: points to the Grafana dashboards. This endpoint brings access to the monitoring dashboards of TeraFlowSDN. The credentials for the <code>admin</code>user are those defined in the <code>my_deploy.sh</code> script, in the <code>TFS_GRAFANA_PASSWORD</code> variable.</li> <li><code>http://127.0.0.1/restconf</code>: points to the Compute component NBI based on RestCONF. This endpoint enables connecting external software, such as ETSI OpenSourceMANO NFV Orchestrator, to TeraFlowSDN.</li> </ul> <p>Note: In the creation of the VM, a forward from host TCP port 8080 to VM's TCP port 80 is configured, so the WebUIs and REST APIs of TeraFlowSDN should be exposed on the endpoint <code>127.0.0.1:8080</code> of your local machine instead of <code>127.0.0.1:80</code>.</p>"},{"location":"deployment_guide/deployment_guide/#15-show-deployment-and-logs","title":"1.5. Show Deployment and Logs","text":"<p>This section presents some helper scripts to inspect the status of the deployment and the logs of the components. These scripts are particularly helpful for troubleshooting during execution of experiments, development, and debugging.</p> Report the deployment of the TFS controller <p></p> <p>The summary report given at the end of the Deploy TFS controller procedure can be generated manually at any time by running the following command. You can avoid sourcing <code>my_deploy.sh</code> if it has been already done.</p> <pre><code>cd ~/tfs-ctrl\nsource my_deploy.sh\n./deploy/show.sh\n</code></pre> <p>Use this script to validate that all the pods, deployments, replica sets, ingress controller, etc. are ready and have the appropriate state, e.g., running for Pods, and the services are deployed and have appropriate IP addresses and port numbers.</p> Report the log of a specific TFS controller component <p></p> <p>A number of scripts are pre-created in the <code>scripts</code> folder to facilitate the inspection of the component logs. For instance, to dump the log of the Context component, run the following command. You can avoid sourcing <code>my_deploy.sh</code> if it has been already done.</p> <pre><code>source my_deploy.sh\n./scripts/show_logs_context.sh\n</code></pre>"},{"location":"development_guide/development_guide/","title":"2. Development Guide","text":""},{"location":"development_guide/development_guide/#21-configure-environment","title":"2.1. Configure Environment","text":""},{"location":"development_guide/development_guide/#211-python","title":"2.1.1. PythonUpgrade the Ubuntu distribution <p>Skip this step if you already did it during the installation of your machine.</p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> Install PyEnv dependencies <p></p> <pre><code>sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget \\\n curl llvm git libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev\n</code></pre> Install PyEnv <p></p> <p>We recommend installing PyEnv through PyEnv Installer. Below you can find the instructions, but we refer you to the link for updated instructions.</p> <pre><code>curl https://pyenv.run | bash\n# When finished, edit ~/.bash_profile // ~/.profile // ~/.bashrc as the installer proposes.\n# In general, it means to append the following lines to ~/.bashrc:\nexport PYENV_ROOT=\"$HOME/.pyenv\"\ncommand -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"\neval \"$(pyenv init -)\"\neval \"$(pyenv virtualenv-init -)\"\n</code></pre> <p>In case .bashrc is not linked properly to your profile, you may need to append the following line into your local .profile file:</p> <pre><code># Open ~/.profile and append this line:\n+source \"$HOME\"/.bashrc\n</code></pre> Restart the machine <p> Restart the machine for all the changes to take effect.</p> <pre><code>sudo reboot\n</code></pre> Install Python 3.9 over PyEnv <p></p> <p>ETSI TeraFlowSDN uses Python 3.9 by default. You should install the latest stable update of Python 3.9, i.e., avoid \"-dev\" versions. To find the latest version available in PyEnv, you can run the following command:</p> <pre><code>pyenv install --list | grep \" 3.9\"\n</code></pre> <p>At the time of writing, this command will output the following list:</p> <pre><code> 3.9.0\n 3.9-dev\n 3.9.1\n 3.9.2\n 3.9.4\n 3.9.5\n 3.9.6\n 3.9.7\n 3.9.8\n 3.9.9\n 3.9.10\n 3.9.11\n 3.9.12\n 3.9.13\n 3.9.14 \n 3.9.15\n 3.9.16 ** always select the latest version **\n</code></pre> <p>Therefore, the latest stable version is Python 3.9.16. To install this version, you should run:</p> <pre><code>pyenv install 3.9.16\n # This command might take some minutes depending on your Internet connection speed \n # and the performance of your machine.\n</code></pre> Create the Virtual Environment for TeraFlowSDN <p> The following commands create a virtual environment named as <code>tfs</code> using Python 3.9 and associate that environment with the current folder, i.e., <code>~/tfs-ctrl</code>. That way, when you are in that folder, the associated virtual environment will be used, thus inheriting the Python interpreter, i.e., Python 3.9, and the Python packages installed on it.</p> <pre><code>cd ~/tfs-ctrl\npyenv virtualenv 3.9.16 tfs\npyenv local 3.9.16/envs/tfs\n</code></pre> <p>After completing these commands, you should see in your prompt that now you're within the virtual environment <code>3.9.16/envs/tfs</code> on folder <code>~/tfs-ctrl</code>:</p> <pre><code>(3.9.16/envs/tfs) tfs@tfs-vm:~/tfs-ctrl$\n</code></pre> <p>In case that the correct pyenv does not get automatically activated when you change to the tfs-ctrl/ folder, then execute the following command:</p> <pre><code>cd ~/tfs-ctrl\npyenv activate 3.9.16/envs/tfs\n</code></pre> Install the basic Python packages within the virtual environment <p> From within the <code>3.9.16/envs/tfs</code> environment on folder <code>~/tfs-ctrl</code>, run the following commands to install the basic Python packages required to work with TeraFlowSDN.</p> <pre><code>cd ~/tfs-ctrl\n./install_requirements.sh\n</code></pre> <p>Some dependencies require to re-load the session, so log-out and log-in again.</p> Generate the Python code from the gRPC Proto messages and services <p></p> <p>The components, e.g., microservices, of the TeraFlowSDN controller, in general, use a gRPC-based open API to interoperate. All the protocol definitions can be found in sub-folder <code>proto</code> within the root project folder. For additional details on gRPC, visit the official web-page gRPC.</p> <p>In order to interact with the components, (re-)generate the Python code from gRPC definitions running the following command:</p> <pre><code>cd ~/tfs-ctrl\nproto/generate_code_python.sh\n</code></pre>","text":"<p>This section describes how to configure the Python environment to run experiments and develop code for the ETSI TeraFlowSDN controller. In particular, we use PyEnv to install the appropriate version of Python and manage the virtual environments.</p>"},{"location":"development_guide/development_guide/#212-java-quarkus","title":"2.1.2. Java (Quarkus) <p>This section describe the steps needed to create a development environment for TFS components implemented in Java. Currently, ZTP and Policy components have been developed in Java (version 11) and use the Quarkus framework, which enables kubernetes-native development.</p> Install JDK <p> To begin, make sure that you have java installed and in the correct version</p> <pre><code>java --version\n</code></pre> <p>If you don't have java installed you will get an error like the following:</p> <pre><code>Command 'java' not found, but can be installed with:\n\nsudo apt install default-jre # version 2:1.11-72build1, or\nsudo apt install openjdk-11-jre-headless # version 11.0.14+9-0ubuntu2\nsudo apt install openjdk-17-jre-headless # version 17.0.2+8-1\nsudo apt install openjdk-18-jre-headless # version 18~36ea-1\nsudo apt install openjdk-8-jre-headless # version 8u312-b07-0ubuntu1\n</code></pre> <p>In which case you should use the following command to install the correct version:</p> <pre><code>sudo apt install openjdk-11-jre-headless\n</code></pre> <p>Else you should get something like the following:</p> <pre><code>openjdk 11.0.18 2023-01-17\nOpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1)\nOpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1, mixed mode, sharing)\n</code></pre> Compiling and testing existing components <p> In the root directory of the existing Java components you will find an executable maven wrapper named <code>mvnw</code>. You could use this executable, which is already configured in pair with the components, instead of your local maven installation. So for example if you want to compile the project you would run the following:</p> <pre><code>./mvnw compile\n</code></pre> VS Code Quarkus plugin <p> In case you are using VS Code for development, we suggest to install the official Quarkus extension. The extension should be able to automatically find the current open project and integrate with the above <code>mvnw</code> maven wrapper, making it easier to control the maven lifecycle. Make sure that you open the specific component directory (i.e., <code>src/ztp</code> or <code>src/policy</code>) and not the general controller one (i.e., <code>src</code>.</p> New Java TFS component <p></p> <p>Sample Project</p> <p>If you want to create a new TFS component written in Java you could generate a new Quarkus project based on the following project:</p> <p>TFS Sample Quarkus Project</p> <p>In that way, you should have most of the libraries you would need to integrate with the rest of the TFS Components. Feel free however to add or remove libraries depending on your needs.</p> <p>Initial setup</p> <p>If you used the sample project above, you should have a project with a basic structure. However there are some steps that you should take before starting development.</p> <p>First make sure that you copy the protobuff files, that are found in the root directory of the TFS SDN controller, to the <code>new-component/src/main/proto</code> directory.</p> <p>Next you should create the following files:</p> <ul> <li><code>new-component/.gitlab-ci.yml</code></li> <li><code>new-component/Dockerfile</code></li> <li><code>new-component/src/resources/application.yaml</code></li> </ul> <p>We suggest to copy the respective files from existing components (Automation and Policy) and change them according to your needs.</p>","text":""},{"location":"development_guide/development_guide/#213-java-maven","title":"2.1.3. Java (Maven) <p>Page under construction</p>","text":""},{"location":"development_guide/development_guide/#214-rust","title":"2.1.4. Rust <p>Page under construction</p>","text":""},{"location":"development_guide/development_guide/#215-erlang","title":"2.1.5. Erlang <p>This section describes how to configure the Erlang environment to run experiments and develop code for the ETSI TeraFlowSDN controller.</p> <p>First we need to install Erlang. There is multiple way, for development we will be using ASDF, a tool that allows the installation of multiple version of Erlang at the same time, and switch from one version to the other at will.</p> <ul> <li>First, install any missing dependencies:</li> </ul> <pre><code>sudo apt install curl git autoconf libncurses-dev build-essential m4 libssl-dev \n</code></pre> <ul> <li>Download ASDF tool to the local account:</li> </ul> <pre><code>git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.10.2\n</code></pre> <ul> <li>Make ASDF activate on login by adding these lines at the end of the <code>~/.bashrc</code> file:</li> </ul> <pre><code>. $HOME/.asdf/asdf.sh\n. $HOME/.asdf/completions/asdf.bash\n</code></pre> <ul> <li>Logout and log back in to activate ASDF.</li> </ul> <p>ASDF supports multiple tools by installing there corresponding plugins.</p> <ul> <li>Install ASDF plugin for Erlang:</li> </ul> <pre><code>asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git\n</code></pre> <ul> <li>Install a version of Erlang:</li> </ul> <pre><code>asdf install erlang 24.3.4.2\n</code></pre> <ul> <li>Activate Erlang locally for TFS controller. This will create a local file called <code>.tool-versions</code> defining which version of the tools to use when running under the current directory:</li> </ul> <pre><code>cd tfs-ctrl/\nasdf local erlang 24.3.4.2\n</code></pre> <p>Erlang projects uses a build tool called rebar3. It is used to manager project dependenecies, compile a project and generate project releases.</p> <ul> <li>Install rebar3 localy from source:</li> </ul> <pre><code>cd ~\ngit clone https://github.com/erlang/rebar3.git\ncd rebar3\nasdf local erlang 24.3.4.2\n./bootstrap\n./rebar3 local install\n</code></pre> <ul> <li>Update <code>~/.bashrc</code> to use rebar3 by adding this line at the end:</li> </ul> <pre><code>export PATH=$HOME/.cache/rebar3/bin:$PATH\n</code></pre> <ul> <li>Logout and log back in.</li> </ul>","text":""},{"location":"development_guide/development_guide/#216-kotlin","title":"2.1.6. Kotlin <p>This section describes the steps needed to establish a development environment for TFS (TeraFlowSDN) components implemented in Kotlin. Currently, the <code>Gateway</code> component stands as the sole component developed in Kotlin.</p> Install Kotlin <p> To begin, make sure that you have kotlin installed and its current version:</p> <pre><code>kotlin -version\n</code></pre> <p>If you don't have kotlin installed you will get an error like the following:</p> <pre><code>Command 'kotlin' not found, but can be installed with:\nsudo snap install --classic kotlin\n</code></pre> <p>In which case you should use the following command to install the correct version:</p> <pre><code> sudo snap install --classic kotlin\n</code></pre> <p>Currently, the recommended version is 1.6.21, which uses Java Runtime Environment (JRE) version 11.</p> Compiling and testing existing components <p> To compile a Kotlin project using Gradle, similarly to using the Maven wrapper (mvnw) for Java projects, you can use the Gradle wrapper (gradlew) within the root directory of your Kotlin component, specifically the gateway directory.</p> <p>Navigate to the gateway directory within your Kotlin project. Ensure that it contains the gradlew script along with the gradle directory. Then, create a directory named <code>proto</code> and move all the files with extension <code>.proto</code> in this way:</p> <pre><code>mkdir proto\ncp ../../../proto/*.proto ./proto \n</code></pre> <p>For building the application, open a terminal or command prompt, navigate to the gateway directory, and run the following command:</p> <pre><code>./gradlew build\n</code></pre> <p>The following program runs the gateway application:</p> <pre><code>./gradlew runServer \n</code></pre> New Kotlin TFS component <p></p> <p>Sample Project</p> <p>If you want to create a new TFS component written in Kotlin you could generate a Kotlin project using <code>gradle</code>. The recommended version is 7.1. Follow the following Gradle guide for its installation. For building the prokect follow this link instead.</p> <p>From inside the new project directory, run the init task using the following command in a terminal: <code>gradle init</code>. </p> <p>The output will look like this:</p> <pre><code>$ gradle init\n\nSelect type of project to generate:\n 1: basic\n 2: application\n 3: library\n 4: Gradle plugin\nEnter selection (default: basic) [1..4] 2\n\nSelect implementation language:\n 1: C++\n 2: Groovy\n 3: Java\n 4: Kotlin\n 5: Scala\n 6: Swift\nEnter selection (default: Java) [1..6] 4\n\nSelect build script DSL:\n 1: Groovy\n 2: Kotlin\nEnter selection (default: Groovy) [1..2] 1\n\nProject name (default: demo):\nSource package (default: demo):\n\n\nBUILD SUCCESSFUL\n2 actionable tasks: 2 executed\n</code></pre> <p>Initial setup</p> <p>The <code>gradle init</code> command generates the new project. </p> <p>First, ensure the protobuf files are copied from the root directory of the TFS SDN controller. Run the following command in the directory of the new project:</p> <pre><code>mkdir proto \ncp TFS/project/root/path/proto/*.proto ./proto/\n</code></pre> <p>The file <code>build.gradle.ktl</code> is fundamental as it manages dependencies. Adjust it for adding external libraries. </p> <p>Next you should create the following files:</p> <ol> <li><code>new-component/.gitlab-ci.yml</code></li> <li><code>new-component/Dockerfile</code></li> </ol> <p>We recommend leveraging the structures and configurations found in the files of existing components for inspiration.</p> <p>Docker Container This project operates with Docker containers. Ensure the production of the container version for your component. To generate the container version of the project, modify the 'new-component/Dockerfile.' Execute the following command from the project's root directory:</p> <pre><code>docker build -t new-image -f new-component/Dockerfile ./\n</code></pre>","text":""},{"location":"development_guide/development_guide/#22-configure-vscode","title":"2.2. Configure VScode","text":"Install VSCode and the required extensions <p>If not already done, install VSCode and the \"Remote SSH\" extension on your local machine, not in the VM.</p> <p>Note: \"Python\" extension is not required here. It will be installed later on the VSCode server running on the VM.</p> Configure the \"Remote SSH\" extension <p></p> <ul> <li>Go to left icon \"Remote Explorer\"</li> <li>Click the \"gear\" icon next to \"SSH TARGETS\" on top of \"Remote Explorer\" bar</li> <li>Choose to edit \"<...>/.ssh/config\" file (or equivalent)</li> <li>Add the following entry (assuming previous port forwarding configuration):</li> </ul> <pre><code>Host TFS-VM\n HostName 127.0.0.1\n Port 2200\n ForwardX11 no\n User tfs\n</code></pre> <ul> <li>Save the file</li> <li>An entry \"TFS-VM\" should appear on \"SSH TARGETS\".</li> </ul> Connect VSCode to the VM through \"Remote SSH\" extension <p></p> <ul> <li>Right-click on \"TFS-VM\"</li> <li>Select \"Connect to Host in Current Window\"</li> <li>Reply to the questions asked</li> <li>Platform of the remote host \"TFS-VM\": Linux</li> <li>\"TFS-VM\" has fingerprint \"\". Do you want to continue?: Continue <li>Type tfs user's password: tfs123</li> <li>You should be now connected to the TFS-VM.</li> <p>Note: if you get a connection error message, the reason might be due to wrong SSH server fingerprint. Edit file \"<...>/.ssh/known_hosts\" on your local user account, check if there is a line starting with \"[127.0.0.1]:2200\" (assuming previous port forwarding configuration), remove the entire line, save the file, and retry connection.</p> Add SSH key to prevent typing the password every time <p> This step creates an SSH key in the VM and installs it on the VSCode to prevent having to type the password every time.</p> <ul> <li>In VSCode (connected to the VM), click menu \"Terminal > New Terminal\"</li> <li>Run the following commands on the VM's terminal through VSCode</li> </ul> <pre><code>ssh-keygen -t rsa -b 4096 -f ~/.ssh/tfs-vm.key\n # leave password empty\nssh-copy-id -i ~/.ssh/tfs-vm.key.pub tfs@10.0.2.10\n # tfs@10.0.2.10's password: <type tfs user's password: tfs123>\nrm .ssh/known_hosts \n</code></pre> <ul> <li>In VSCode, click left \"Explorer\" panel to expand, if not expanded, and click \"Open Folder\" button.</li> <li>Choose \"/home/tfs/\"</li> <li>Type tfs user's password when asked</li> <li>Trust authors of the \"/home/tfs [SSH: TFS-VM]\" folder when asked</li> <li>Right click on the file \"tfs-vm.key\" in the file explorer</li> <li>Select \"Download...\" option</li> <li>Download the file into your user's accout \".ssh\" folder</li> <li> <p>Delete files \"tfs-vm.key\" and \"tfs-vm.key.pub\" on the TFS-VM.</p> </li> <li> <p>In VSCode, click left \"Remote Explorer\" panel to expand</p> </li> <li>Click the \"gear\" icon next to \"SSH TARGETS\" on top of \"Remote Explorer\" bar</li> <li>Choose to edit \"<...>/.ssh/config\" file (or equivalent)</li> <li>Find entry \"Host TFS-VM\" and update it as follows:</li> </ul> <pre><code>Host TFS-VM\n HostName 127.0.0.1\n Port 2200\n ForwardX11 no\n User tfs\n IdentityFile \"<path to the downloaded identity private key file>\"\n</code></pre> <ul> <li>Save the file</li> <li>From now, VSCode will use the identity file to connect to the TFS-VM instead of the user's password.</li> </ul> Install VSCode Python Extension (in VSCode server) <p> This step installs Python extensions in VSCode server running in the VM.</p> <ul> <li>In VSCode (connected to the VM), click left button \"Extensions\"</li> <li>Search \"Python\" extension in the extension Marketplace.</li> <li> <p>Install official \"Python\" extension released by Microsoft.</p> <ul> <li>By default, since you're connected to the VM, it will be installed in the VSCode server running in the VM.</li> </ul> </li> <li> <p>In VSCode (connected to the VM), click left button \"Explorer\"</p> </li> <li>Click \"Ctrl+Alt+P\" and type \"Python: Select Interpreter\". Select option \"Python: 3.9.13 64-bit ('tfs')\"</li> </ul> Define environment variables for VSCode <p> The source code in the TFS controller project is hosted in folder <code>src/</code>. To help VSCode find the Python modules and packages, add the following file into your working space root folder:</p> <pre><code>echo \"PYTHONPATH=./src\" >> ~/tfs-ctrl/.env\n</code></pre>"},{"location":"development_guide/development_guide/#23-develop-a-component-wip","title":"2.3. Develop A Component (WIP)","text":"<p>Page under construction</p>"},{"location":"testing/postman/","title":"Postman","text":"<p>In this section we can use Postman to publish an API as a provider and use it as an invoker.</p>"},{"location":"testing/postman/#requisites","title":"Requisites","text":"<ul> <li>We will need to have Node.js installed since we will use a small script to create the CSRs of the certificates.</li> <li>An instance of CAPIF (If it is not local, certain variables would have to be modified both in the Node.js script and in the Postman environment variables).</li> </ul>"},{"location":"testing/postman/#first-steps","title":"First steps","text":"<ol> <li>Install the Node dependencies package.json to run the script with:</li> </ol> <pre><code>npm i\n</code></pre> <ol> <li>Run the script.js with the following command:</li> </ol> <pre><code>node script.js\n</code></pre> <ol> <li>Import Postman collection and environment variables (CAPIF.postman_collection.json and CAPIF.postman_environment.json)</li> <li>Select CAPIF Environment before start testing.</li> </ol>"},{"location":"testing/postman/#remote-capif","title":"Remote CAPIF","text":"<p>If the CAPIF is not local, the host and port of both the CAPIF and the register would have to be specified in the variables, and the CAPIF_HOSTNAME in the script, necessary to obtain the server certificate.</p> <p>Enviroments in Postman</p> <pre><code>CAPIF_HOSTNAME capifcore\nCAPIF_PORT 8080\nREGISTER_HOSTNAME register\nREGISTER_PORT 8084\n</code></pre> <p>Const in script.js</p> <pre><code>CAPIF_HOSTNAME capifcore\n</code></pre>"},{"location":"testing/postman/#capif-flows","title":"CAPIF Flows","text":"<p>Once the first steps have been taken, we can now use Postman requests. These requests are numbered in the order that must be followed to obtain everything necessary from CAPIF.</p>"},{"location":"testing/postman/#creation-of-user-by-admin","title":"Creation of User by Admin","text":"<p>The first step would be for an administrator to create a user with which a provider and an invoker will be created. To do this, the admin must log in to obtain the token needed in admin requests.</p>"},{"location":"testing/postman/#01-login_admin","title":"01-Login_admin","text":""},{"location":"testing/postman/#02-creation-of-user","title":"02-Creation of User","text":""},{"location":"testing/postman/#publication-of-an-api","title":"Publication of an API","text":"<p>The next step is to register a provider using the user created by the administrator in order to publish an API.</p>"},{"location":"testing/postman/#03-getauth_provider","title":"03-getauth_provider","text":""},{"location":"testing/postman/#04-onboard_provider","title":"04-onboard_provider","text":"<p>At this point we move on to using certificate authentication in CAPIF. In Postman it is necessary to add the certificates manually and using more than one certificate for the same host as we do in CAPIF complicates things. For this reason, we use the script to overwrite a certificate and a key when it is necessary to have a specific one.</p> <p>To configure go to settings in Postman and open the certificates section. </p> <ul> <li>Here, activate the CA certificates option and add the ca_cert.pem file found in the Responses folder.</li> <li>Adds a client certificate specifying the CAPIF host being used and the files client_cert.crt and client_key.key in the Responses folder.</li> </ul> <p>Once this is done, the node script will be in charge of changing the certificate that is necessary in each request.</p>"},{"location":"testing/postman/#05-publish_api","title":"05-publish_api","text":"<p>Once the api is published, we can start it. In this case we have a test one created in python called hello_api.py that can be executed with the following command:</p> <pre><code>python3 hello_api.py\n</code></pre> <p>The API publication interface is set to localhost with port 8088, so the service must be set up locally. If you wanted to build it on another site, you would have to change the interface description in the body of publish_api.</p> <p>With this the provider part would be finished.</p>"},{"location":"testing/postman/#calling-the-api","title":"Calling the API","text":"<p>Finally, we will create an invoker with the user given by the administrator to be able to use the published api.</p>"},{"location":"testing/postman/#06-getauth_invoker","title":"06-getauth_invoker","text":""},{"location":"testing/postman/#07-onboard_invoker","title":"07-onboard_invoker","text":"<p>At this point we move on to using certificate authentication in CAPIF. If you did not configure the provider's certificates, you would have to do it now.</p>"},{"location":"testing/postman/#08-discover","title":"08-discover","text":""},{"location":"testing/postman/#09-security_context","title":"09-security_context","text":""},{"location":"testing/postman/#10-get_token","title":"10-get_token","text":""},{"location":"testing/postman/#11-call_service","title":"11-call_service","text":"<p>With this, we would have made the API call and finished the flow.</p>"},{"location":"testing/postman/#other-requests","title":"Other requests","text":"<p>Other requests that we have added are the following:</p> <ul> <li>offboard_provider Performs offboarding of the provider, thereby eliminating the published APIs.</li> <li>offboard_invoker Offboards the invoker, also eliminating access to the APIs of that invoker.</li> <li>remove_user Delete the user.</li> <li>refresh_admin_token Return a new access token to the admin.</li> </ul>"},{"location":"testing/postman/#notes","title":"Notes","text":"<ul> <li>This process is designed to teach how requests are made in Postman and the flow that should be followed to publish and use an API.</li> <li>It is possible that if external CAPIFs are used (Public CAPIF) the test data may already be used or the API already registered.</li> <li>It is necessary to have the Node service running to make the certificate change for the requests, otherwise it will not work.</li> <li>We are working on adding more requests to the Postman collection.</li> <li>This collection is a testing guide and is recommended for testing purposes only.</li> </ul>"},{"location":"testing/robotframework/","title":"Robot Framework","text":""},{"location":"testing/robotframework/#steps-to-test","title":"Steps to Test","text":"<p>To run any test locally you will need docker and docker-compose installed in order run services and execute test plan. Steps will be:</p> <ul> <li> <p>Run All Services: See section Run All CAPIF Services</p> </li> <li> <p>Run desired tests: At this point we have 2 options:</p> </li> <li> <p>Using helper script: Script Test Execution</p> </li> <li>Build robot docker image and execute manually robot docker: Manual Build And Test Execution</li> </ul>"},{"location":"testing/robotframework/#script-test-execution","title":"Script Test Execution","text":"<p>This script will build robot docker image if it's need and execute tests selected by \"include\" option. Just go to service folder, execute and follow steps.</p> <pre><code>./run_capif_tests.sh --include <TAG>\n</code></pre> <p>Results will be stored at /results <p>Please check parameters (include) under Test Execution at Manual Build And Test Execution.</p>"},{"location":"testing/robotframework/#mock-server","title":"Mock Server","text":"<p>Some tests on Test Plans require mockserver. That mock server must be deployed and reachable by Robot Framework and CCF under test.</p> <p>To run Mock Server locally you can just execute the next script:</p> <pre><code>cd services\n./run_mock_server.sh\n\nor\n./run.sh -s\n</code></pre> <p>If you want to launch only tests that not needed mockserver, just add \"--exclude mockserver\" parameter to robot execution:</p> <pre><code>./run_capif_tests.sh --include <TAG> --exclude mockserver\n</code></pre> <p>After run tests the Mock Server can be removed from local deployment:</p> <pre><code>./clean_mock_server.sh\n\nor\n./clean_capif_docker_services.sh -s\n</code></pre>"},{"location":"testing/robotframework/#manual-build-and-test-execution","title":"Manual Build And Test Execution","text":"<ul> <li>Build Robot docker image:</li> </ul> <pre><code>cd tools/robot\ndocker build . -t capif-robot-test:latest\n</code></pre> <ul> <li>Tests Execution:</li> </ul> <p>Execute all tests locally:</p> <pre><code><PATH_TO_REPOSITORY>=path in local machine to repository cloned.\n<PATH_RESULT_FOLDER>=path to a folder on local machine to store results of Robot Framework execution.\n<CAPIF_HOSTNAME>=Is the hostname set when run.sh is executed, by default it is capifcore.\n<CAPIF_HTTP_PORT>=This is the port to reach when robot framework want to reach CAPIF deployment using http, this should be set to port without TLS set on Nginx, 8080 by default.\n<CAPIF_HTTPS_PORT>=This is the port to be used when we want to use https connection, this should be set to port with TLS set on Nginx, 443 by default\n<CAPIF_REGISTER>=This is the hostname of register service deployed. By default it is register.\n<CAPIF_REGISTER_PORT>=This is the port to be used to reach register service deployed. By default it is 8084.\n<CAPIF_VAULT>=This is the hostname of vault service. By default it is vault.\n<CAPIF_VAULT_PORT>=This is the port to be used to reach vault service. By default it is 8200.\n<CAPIF_VAULT_TOKEN>=Vault token to be used on request through vault. By default it is \"read-ca-token\".\n<MOCK_SERVER_URL>=Setup Mock server url to be used in notifications at tests marked with mockserver tag. By default it is not set.\n\nTo execute all tests run :\ndocker run -ti --rm --network=\"host\" \\\n --add-host host.docker.internal:host-gateway \\\n --add-host vault:host-gateway \\\n --add-host register:host-gateway \\\n --add-host mock-server:host-gateway \\\n -v <PATH_TO_REPOSITORY>/tests:/opt/robot-tests/tests \\\n -v <PATH_RESULT_FOLDER>:/opt/robot-tests/results capif-robot-test:latest \\\n --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \\\n --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \\\n --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \\\n --variable CAPIF_REGISTER:$CAPIF_REGISTER \\\n --variable CAPIF_REGISTER_PORT:$CAPIF_REGISTER_PORT \\\n --variable CAPIF_VAULT:$CAPIF_VAULT \\\n --variable CAPIF_VAULT_PORT:$CAPIF_VAULT_PORT \\\n --variable CAPIF_VAULT_TOKEN:$CAPIF_VAULT_TOKEN \\\n --variable MOCK_SERVER_URL:$MOCK_SERVER_URL \\\n --include all\n</code></pre> <p>Execute specific tests locally:</p> <pre><code>To run more specific tests, for example, only one functionality:\n<TAG>=Select one from list:\n \"capif_api_acl\",\n \"capif_api_auditing_service\",\n \"capif_api_discover_service\",\n \"capif_api_events\",\n \"capif_api_invoker_management\",\n \"capif_api_logging_service\",\n \"capif_api_provider_management\",\n \"capif_api_publish_service\",\n \"capif_security_api\n\nAnd Run:\ndocker run -ti --rm --network=\"host\" \\\n --add-host host.docker.internal:host-gateway \\\n --add-host vault:host-gateway \\\n --add-host register:host-gateway \\\n --add-host mock-server:host-gateway \\\n -v <PATH_TO_REPOSITORY>/tests:/opt/robot-tests/tests \\\n -v <PATH_RESULT_FOLDER>:/opt/robot-tests/results capif-robot-test:latest \\\n --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \\\n --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \\\n --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \\\n --variable CAPIF_REGISTER:$CAPIF_REGISTER \\\n --variable CAPIF_REGISTER_PORT:$CAPIF_REGISTER_PORT \\\n --variable CAPIF_VAULT:$CAPIF_VAULT \\\n --variable CAPIF_VAULT_PORT:$CAPIF_VAULT_PORT \\\n --variable CAPIF_VAULT_TOKEN:$CAPIF_VAULT_TOKEN \\\n --variable MOCK_SERVER_URL:$MOCK_SERVER_URL \\\n --include <TAG>\n</code></pre>"},{"location":"testing/robotframework/#test-result-review","title":"Test result review","text":"<p>In order to Review results after tests, you can check general report at /report.html or if you need more detailed information /log.html, example: <ul> <li> <p>Report: </p> </li> <li> <p>Detailed information: </p> </li> </ul> <p>NOTE: If you need more detail at Robot Framework Logs you can set log level option just adding to command --loglevel DEBUG</p>"},{"location":"testing/testplan/","title":"Test Plan Index","text":"<p>List of Common API Services implemented:</p> <ul> <li>Common Operations</li> <li>Api Invoker Management</li> <li>Api Provider Management</li> <li>Api Publish Service</li> <li>Api Discover Service</li> <li>Api Events Service</li> <li>Api Security Service</li> <li>Api Logging Service</li> <li>Api Auditing Service</li> <li>Api Access Control Policy</li> </ul>"},{"location":"testing/testplan/api_access_control_policy/","title":"Test Plan for CAPIF Api Access Control Policy","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_access_control_policy/#test-case-1-retrieve-acl","title":"Test Case 1: Retrieve ACL","text":"<p>Test ID: capif_api_acl-1</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL from CAPIF</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain only one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-2-retrieve-acl-with-2-service-apis-published","title":"Test Case 2: Retrieve ACL with 2 Service APIs published","text":"<p>Test ID: capif_api_acl-2</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL from CAPIF for 2 different serviceApis published.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had two Service API Published on CAPIF</li> <li>API Invoker had a Security Context for both Service APIs published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information for service_1.</li> <li>Provider Get ACL information for service_2.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId1</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId2</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId2}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-3-retrieve-acl-with-security-context-created-by-two-different-invokers","title":"Test Case 3: Retrieve ACL with security context created by two different Invokers","text":"<p>Test ID: capif_api_acl-3</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL from CAPIF containing 2 objects.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>Two API Invokers had a Security Context for same Service API published by provider.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Repeat previous 3 steps in order to have a new Invoker.</p> </li> <li> <p>Provider Retrieve ACL for serviceApiId</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain two objects.</li> <li>One object must match with apiInvokerId1 and the other one with apiInvokerId2 an registered previously.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-4-retrieve-acl-filtered-by-api-invoker-id","title":"Test Case 4: Retrieve ACL filtered by api-invoker-id","text":"<p>Test ID: capif_api_acl-4</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL filtering by apiInvokerId from CAPIF containing 1 objects.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>Two API Invokers had a Security Context for same Service API published by provider.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information with query parameter indicating first api-invoker-id.</li> <li>Provider Get ACL information with query parameter indicating second api-invoker-id.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Repeat previous 3 steps in order to have a new Invoker.</p> </li> <li> <p>Provider Retrieve ACL for serviceApiId1</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={apiInvokerId1}</li> <li>Use serviceApiId, aefId and apiInvokerId1</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId2</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={apiInvokerId2}</li> <li>Use serviceApiId, aefId and apiInvokerId2</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with apiInvokerId1.</li> </ol> </li> </ol> </li> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with apiInvokerId2.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-5-retrieve-acl-filtered-by-supported-features","title":"Test Case 5: Retrieve ACL filtered by supported-features","text":"<p>Test ID: capif_api_acl-5</p> <p>Description:</p> <p>CURRENTLY NOT SUPPORTED FEATURE</p> <p>This test case will check that an API Provider can retrieve ACL filtering by supportedFeatures from CAPIF containing 1 objects.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>Two API Invokers had a Security Context for same Service API published by provider.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information with query parameter indicating first supported-features.</li> <li>Provider Get ACL information with query parameter indicating second supported-features.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Repeat previous 3 steps in order to have a new Invoker.</p> </li> <li> <p>Provider Retrieve ACL for serviceApiId</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}&supported-features={apiInvokerId1}</li> <li>Use serviceApiId, aefId and apiInvokerId1</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}&supported-features={apiInvokerId2}</li> <li>Use serviceApiId, aefId and apiInvokerId2</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with supportedFeatures1.</li> </ol> </li> </ol> </li> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with supportedFeatures1.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-6-retrieve-acl-with-aef-id-not-valid","title":"Test Case 6: Retrieve ACL with aef-id not valid","text":"<p>Test ID: capif_api_acl-6</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF if aef-id is not valid</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${AEF_ID_NOT_VALID}</li> <li>Use serviceApiId and AEF_ID_NOT_VALID</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {service_api_id}, aef_id: {aef_id}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-7-retrieve-acl-with-service-id-not-valid","title":"Test Case 7: Retrieve ACL with service-id not valid","text":"<p>Test ID: capif_api_acl-7</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF if service-api-id is not valid</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${NOT_VALID_SERVICE_API_ID}?aef-id=${aef_id}</li> <li>Use NOT_VALID_SERVICE_API_ID and aef_id</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {service_api_id}, aef_id: {aef_id}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-8-retrieve-acl-with-service-api-id-and-aef-id-not-valid","title":"Test Case 8: Retrieve ACL with service-api-id and aef-id not valid","text":"<p>Test ID: capif_api_acl-8</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF if service-api-id and aef-id are not valid</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${NOT_VALID_SERVICE_API_ID}?aef-id=${AEF_ID_NOT_VALID}</li> <li>Use NOT_VALID_SERVICE_API_ID and aef_id</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-9-retrieve-acl-without-securitycontext-created-previously-by-invoker","title":"Test Case 9: Retrieve ACL without SecurityContext created previously by Invoker","text":"<p>Test ID: capif_api_acl-9</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL if no invoker had requested Security Context to CAPIF</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker created but no Security Context for Service API published had been requested.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li> <p>Discover published APIs</p> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-10-retrieve-acl-filtered-by-api-invoker-id-not-present","title":"Test Case 10: Retrieve ACL filtered by api-invoker-id not present","text":"<p>Test ID: capif_api_acl-10</p> <p>Description:</p> <p>This test case will check that an API Provider get not found response if filter by not valid api-invoker-id doesn't match any registered ACL.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={NOT_VALID_API_INVOKER_ID}</li> <li>Use serviceApiId, aefId and NOT_VALID_API_INVOKER_ID</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-11-retrieve-acl-with-apf-certificate","title":"Test Case 11: Retrieve ACL with APF Certificate","text":"<p>Test ID: capif_api_acl-11</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF using APF Certificate</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use APF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-12-retrieve-acl-with-amf-certificate","title":"Test Case 12: Retrieve ACL with AMF Certificate","text":"<p>Test ID: capif_api_acl-12</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF using AMF Certificate</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AMF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-13-retrieve-acl-with-invoker-certificate","title":"Test Case 13: Retrieve ACL with Invoker Certificate","text":"<p>Test ID: capif_api_acl-13</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF using Invoker Certificate</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-14-no-acl-for-invoker-after-be-removed","title":"Test Case 14: No ACL for invoker after be removed","text":"<p>Test ID: capif_api_acl-14</p> <p>Description:</p> <p>This test case will check that ACLs are removed after invoker is removed.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published and ACL is present</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information of invoker.</li> <li>Remove Invoker from CAPIF.</li> <li>Provider Get ACL information of invoker.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={api-invoker-id}</li> <li>Use serviceApiId, aefId and api-invoker-id</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li>Remove Invoker from CAPIF</li> <li>Provider Retrieve ACL<ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={api-invoker-id}</li> <li>Use serviceApiId, aefId and api-invoker-id</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result: 1. ACL Response: 1. 200 OK Response. 2. body returned must accomplish AccessControlPolicyList data structure. 3. apiInvokerPolicies must: 1. contain only one object. 2. apiInvokerId must match apiInvokerId registered previously.</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: None and supportedFeatures: None\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/","title":"Test Plan for CAPIF Api Auditing Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_auditing_service/#test-case-1-get-capif-log-entry","title":"Test Case 1: Get CAPIF Log Entry.","text":"<p>Test ID: capif_api_auditing-1</p> <p>Description:</p> <p>This test case will check that a CAPIF AMF can get log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>200 OK</li> <li>Response Body must follow InvocationLog data structure with:<ul> <li>aefId</li> <li>apiInvokerId</li> <li>logs</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-2-get-capif-log-entry-with-no-log-entry-in-capif","title":"Test Case 2: Get CAPIF Log Entry With no Log entry in CAPIF.","text":"<p>Test ID: capif_api_auditing-2</p> <p>Description:</p> <p>This test case will check that a CAPIF AEF can create log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found Log Entry in CAPIF\".</li> <li>cause with message \"Not Exist Logs with the filters applied\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-3-get-capif-log-entry-without-aef-id-and-api-invoker-id","title":"Test Case 3: Get CAPIF Log Entry without aef-id and api-invoker-id.","text":"<p>Test ID: capif_api_auditing-3</p> <p>Description:</p> <p>This test case will check that a CAPIF AEF can create log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is no pre-authorised (has no valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>400 Bad Request</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 400</li> <li>title with message \"Bad Request\"</li> <li>detail with message \"aef_id and api_invoker_id parameters are mandatory\".</li> <li>cause with message \"Mandatory parameters missing\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-4-get-capif-log-entry-with-filtter-api-version","title":"Test Case 4: Get CAPIF Log Entry with filtter api-version.","text":"<p>Test ID: capif_api_auditing-4</p> <p>Description:</p> <p>This test case will check that a CAPIF AMF can get log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}&api-version={v1}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>200 OK</li> <li>Response Body must follow InvocationLog data structure with:<ul> <li>aefId</li> <li>apiInvokerId</li> <li>logs</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-5-get-capif-log-entry-with-filter-api-version-but-not-exist-in-log-entry","title":"Test Case 5: Get CAPIF Log Entry with filter api-version but not exist in log entry.","text":"<p>Test ID: capif_api_auditing-4</p> <p>Description:</p> <p>This test case will check that a CAPIF AMF can get log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}&api-version={v58}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>detail with message \"Parameters do not match any log entry\"</li> <li>cause with message \"No logs found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/","title":"Test Plan for CAPIF Discover Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_discover_service/#test-case-1-discover-published-service-apis-by-authorised-api-invoker","title":"Test Case 1: Discover Published service APIs by Authorised API Invoker","text":"<p>Test ID: capif_api_discover_service-1</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs:<ul> <li>Send GET to https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains the API Published previously</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-2-discover-published-service-apis-by-non-authorised-api-invoker","title":"Test Case 2: Discover Published service APIs by Non Authorised API Invoker","text":"<p>Test ID: capif_api_discover_service-2</p> <p>Description:</p> <p>This test case will check that an API Publisher can't discover published APIs because is not authorized.</p> <p>Pre-Conditions:</p> <ul> <li>Service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by no invoker entity</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs by no invoker entity:<ul> <li>Send GET to https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Use not Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Discover Request By no invoker entity:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-3-discover-published-service-apis-by-not-registered-api-invoker","title":"Test Case 3: Discover Published service APIs by not registered API Invoker","text":"<p>Test ID: capif_api_discover_service-3</p> <p>Description:</p> <p>This test case will check that a not registered invoker is forbidden to discover published APIs.</p> <p>Pre-Conditions:</p> <ul> <li>Service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Publisher</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs with not valid apiInvoker:<ul> <li>Send GET to https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={INVOKER_NOT_REGISTERED}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Discover Request By Invoker:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"API Invoker does not exist\".</li> <li>cause with message \"API Invoker id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-4-discover-published-service-apis-by-registered-api-invoker-with-1-result-filtered","title":"Test Case 4: Discover Published service APIs by registered API Invoker with 1 result filtered","text":"<p>Test ID: capif_api_discover_service-4</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>At least 2 Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 and service_2 at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Discover filtered by api-name service_1 Service APIs by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs filtering by api-name:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}&api-name=service_1**</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> <li>filter by api-name service_1</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Publish request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains previously registered Service APIs published.</li> </ul> </li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains only Service API published with api-name service_1</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-5-discover-published-service-apis-by-registered-api-invoker-filtered-with-no-match","title":"Test Case 5: Discover Published service APIs by registered API Invoker filtered with no match","text":"<p>Test ID: capif_api_discover_service-5</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>At least 2 Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 and service_2 at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Discover filtered by api-name not published Service APIs by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs filtering by api-name not published:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}&api-name=NOT_VALID_NAME</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> <li>filter by api-name NOT_VALID_NAME</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Publish request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains previously registered Service APIs published.</li> </ul> </li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>404 Not Found response.</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"API Invoker {api_invoker_id} has no API Published that accomplish filter conditions\".</li> <li>cause with message \"No API Published accomplish filter conditions\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-6-discover-published-service-apis-by-registered-api-invoker-not-filtered","title":"Test Case 6: Discover Published service APIs by registered API Invoker not filtered","text":"<p>Test ID: capif_api_discover_service-6</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>2 Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 and service_2 at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Discover without filter by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs not filtered:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Discover Request By Invoker:</p> <ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains the 2 previously registered Service APIs published.</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/","title":"Test Plan for CAPIF Api Events Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_events_service/#test-case-1-creates-a-new-individual-capif-event-subscription","title":"Test Case 1: Creates a new individual CAPIF Event Subscription.","text":"<p>Test ID: capif_api_events-1</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) can Subscribe to Events</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-2-creates-a-new-individual-capif-event-subscription-with-invalid-subscriberid","title":"Test Case 2: Creates a new individual CAPIF Event Subscription with Invalid SubscriberId","text":"<p>Test ID: capif_api_events-2</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) cannot Subscribe to Events without valid SubcriberId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is not pre-authorised (has invalid InvokerId or apfId)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{SUBSCRIBER_NOT_REGISTERED}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker or APF or AEF or AMF Not found\".</li> <li>cause with message \"Subscriber Not Found\".</li> </ul> </li> </ol> </li> <li> <p>Event Subscriptions are not stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-3-deletes-an-individual-capif-event-subscription","title":"Test Case 3: Deletes an individual CAPIF Event Subscription","text":"<p>Test ID: capif_api_events-3</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) can Delete an Event Subscription</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> <li>Remove Event Subscription</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Remove Event Subscription:</p> <ol> <li>Send DELETE to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> <li> <p>Remove Event Subscription:</p> <ol> <li>204 No Content</li> </ol> </li> <li> <p>Event Subscription is not present at CAPIF Database.</p> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-4-deletes-an-individual-capif-event-subscription-with-invalid-subscriberid","title":"Test Case 4: Deletes an individual CAPIF Event Subscription with invalid SubscriberId","text":"<p>Test ID: capif_api_events-4</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) cannot Delete to Events without valid SubcriberId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId).</li> <li>CAPIF subscriber is subscribed to Events.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve Location Header with subscriptionId.</li> <li>Remove Event Subscribed with not valid Subscriber.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Remove Event Subcription with not valid subscriber:</p> <ol> <li>Send DELETE to https://{CAPIF_HOSTNAME}/capif-events/v1/{SUBSCRIBER_ID_NOT_VALID}/subscriptions/{subcriptionId}</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> <li> <p>Error Response Body must accomplish with ProblemDetails data structure with:</p> <ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker or APF or AEF or AMF Not found\".</li> <li>cause with message \"Subscriber Not Found\".</li> </ul> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-5-deletes-an-individual-capif-event-subscription-with-invalid-subscriptionid","title":"Test Case 5: Deletes an individual CAPIF Event Subscription with invalid SubscriptionId","text":"<p>Test ID: capif_api_events-5</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) cannot Delete an Event Subscription without valid SubscriptionId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has invalid InvokerId or apfId).</li> <li>CAPIF subscriber is subscribed to Events.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve Location Header with subscriptionId.</li> <li>Remove Event Subscribed with not valid Subscriber.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Remove Event Subcription with not valid subscriber:</p> <ol> <li>Send DELETE to to https://{CAPIF_HOSTNAME}/capif-events/v1/{subcriberId}/subscriptions/{SUBSCRIPTION_ID_NOT_VALID}</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> <li>Remove Event Subscription with not valid subscriber:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>detail with message \"Service API not existing\".</li> <li>cause with message \"Event API subscription id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-6-invoker-receives-service-api-invocation-events","title":"Test Case 6: Invoker receives Service API Invocation events","text":"<p>Test ID: capif_api_events-6, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE, receive the notification when AEF Send TO logging service result of invocations to their APIs.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered and published APIs.</li> <li>API Provider had a Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register provider and publish one API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover published APIs and extract apiIds and apiNames</li> <li>Subscribe to SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE event filtering by aefId.</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> <li>Emulate Success and Failure on API invocation of provider by Invoker, using Invocation Logs API.</li> </ol> <p>Information of Test:</p> <ol> <li>Perform provider registration</li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform invoker onboarding</p> </li> <li> <p>Discover published APIs:</p> <ul> <li>Get Api Ids And Api Names from response.</li> </ul> </li> <li> <p>Event Subscription to SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE of provider previously registered:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['SERVICE_API_INVOCATION_SUCCESS','SERVICE_API_INVOCATION_FAILURE']</li> <li>eventFilter: only receive events from provider's aefId.</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Create Log Entry emulating provider receive Success and Failure api invocation from invoker:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body with:<ol> <li>aefId from provider published.</li> <li>apiInvokerId from invoker onboarded.</li> <li>apiId of published API</li> <li>apiName of published API</li> <li>200 and 400 results in two logs.</li> </ol> </li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Response to creation of log entry on CCF must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/api-invocation-logs/{apiVersion}/{aefId}/subscriptions/{logId}</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>Two Events have been received.</li> <li>Validate received events follow EventNotification data structure, with invocationLog in eventDetail parameter.<ol> <li>One should be SERVICE_API_INVOCATION_SUCCESS related with 200 result at Log.</li> <li>The other one must be SERVICE_API_INVOCATION_FAILURE related with 400 result at Log.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-7-invoker-subscribe-to-service-api-available-and-unavailable-events","title":"Test Case 7: Invoker subscribe to Service API Available and Unavailable events","text":"<p>Test ID: capif_api_events-7, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE, receive the notification when AEF publish and remove it. </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered and published APIs.</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register provider and publish one API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover published APIs and extract apiIds and apiNames</li> <li>Subscribe to SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE event filtering by aefId.</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> <li>Provider publish new API.</li> <li>Provider remove published API.</li> </ol> <p>Information of Test:</p> <ol> <li>Perform provider registration</li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform invoker onboarding</p> </li> <li> <p>Discover published APIs:</p> <ul> <li>Get Api Ids And Api Names from response.</li> </ul> </li> <li> <p>Event Subscription to SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE of provider previously registered:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['SERVICE_API_AVAILABLE','SERVICE_API_UNAVAILABLE']</li> <li>eventFilter: only receive events from provider's aefId.</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Publish new Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_2</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Remove published Service API at CCF:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Mock Server received messages must accomplish:</p> <ol> <li>Two Events have been received.</li> <li>Validate received events follow EventNotification data structure, with apiIds in eventDetail parameter.<ol> <li>One should be SERVICE_API_AVAILABLE apiId of service_2 published API.</li> <li>The other one must be SERVICE_API_UNAVAILABLE apiId of service_1 published API.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-8-invoker-subscribe-to-service-api-update","title":"Test Case 8: Invoker subscribe to Service API Update","text":"<p>Test ID: capif_api_events-8, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to SERVICE_API_UPDATE, receive the notification when AEF Update some information on API Published.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered and published APIs.</li> <li>API Provider had a Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider and publish one API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover published APIs and extract apiIds and apiNames</li> <li>Subscribe to SERVICE_API_UPDATE event filtering by aefId.</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header at event subscription</li> <li>Provider update information of Service API Published.</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> <li>Store serviceApiId</li> </ul> </li> <li> <p>Perform invoker onboarding</p> </li> <li> <p>Discover published APIs:</p> <ul> <li>Get Api Ids And Api Names from response.</li> </ul> </li> <li> <p>Event Subscription to SERVICE_API_UPDATE of provider previously registered:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['SERVICE_API_UPDATE']</li> <li>eventFilter: only receive events from provider's aefId.</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>body [service api description] with overrided apiName to service_1_modified**</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Response to Update Published Service API:<ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1_modified**</li> </ul> </li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>One Event has been received.</li> <li>Validate received events follow EventNotification data structure, with serviceAPIDescriptions in eventDetail parameter.<ol> <li>Event should be SERVICE_API_UPDATE with eventDetail with modified apiName.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-9-provider-subscribe-to-api-invoker-events","title":"Test Case 9: Provider subscribe to API Invoker events","text":"<p>Test ID: capif_api_events-9, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Provider subscribed to API Invoker events (API_INVOKER_ONBOARDED, API_INVOKER_UPDATED and API_INVOKER_OFFBOARDED), receive the notifications when Invoker is onboarded, updated and removed respectively.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Subscribe Provider to API_INVOKER_ONBOARDED, API_INVOKER_UPDATED and API_INVOKER_OFFBOARDED events.</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Update Onboarding Information at CCF with a minor change on \"notificationDestination\"</li> <li>Offboard Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Event Subscription to API_INVOKER_ONBOARDED, API_INVOKER_UPDATED and API_INVOKER_OFFBOARDED events:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['API_INVOKER_ONBOARDED', 'API_INVOKER_UPDATED', 'API_INVOKER_OFFBOARDED']</li> </ol> </li> <li>Use Provider AMF Certificate</li> </ol> </li> <li>Perform invoker onboarding</li> <li>Update information of previously onboarded Invoker:<ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>\"notificationDestination\": \"http://host.docker.internal:8086/netapp_new_callback\",</li> </ul> </li> <li>Offboard:<ul> <li>Send DELETE to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Update Request (PUT) with minor change must contain:<ol> <li>200 OK response.</li> <li>notificationDestination on response must contain the new value</li> </ol> </li> <li>Response to Offboard Request (DELETE) must contain:<ol> <li>204 No Content</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>Three Events have been received.</li> <li>Validate received events follow EventNotification data structure, with apiInvokerIds in eventDetail parameter.<ol> <li>One Event should be API_INVOKER_ONBOARDED with eventDetail with modified apiInvokerId.</li> <li>One Event should be API_INVOKER_UPDATED with eventDetail with modified apiInvokerId.</li> <li>One Event should be API_INVOKER_OFFBOARDED with eventDetail with modified apiInvokerId.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-10-provider-subscribed-to-acl-update-event","title":"Test Case 10: Provider subscribed to ACL update event","text":"<p>Test ID: capif_api_events-10, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Provider subscribed to ACCESS_CONTROL_POLICY_UPDATE receive a notification when ACL Changes.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>API Provider had one Service API Published on CAPIF</li> <li>API Invoker had a Security Context for the Service API published by provider.</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF.</li> <li>Publish a provider API with name service_1.</li> <li>Register Invoker and Onboard Invoker at CCF.</li> <li>Subscribe Provider to ACCESS_CONTROL_POLICY_UPDATE event.</li> <li>Discover APIs filtered by aef_id</li> <li>Create Security Context for Invoker.</li> <li>Provider Retrieve ACL</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Perform invoker onboarding</li> <li>Event Subscription to ACCESS_CONTROL_POLICY_UPDATE event:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['ACCESS_CONTROL_POLICY_UPDATE']</li> <li>eventFilters: apiInvokerIds array with apiInvokerId of invoker</li> </ol> </li> <li>Use Provider AMF Certificate</li> </ol> </li> <li>Discover published APIs</li> <li>Create Security Context for Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li>Provider Retrieve ACL<ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain only one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>One Event has been received.</li> <li>Validate received event follow EventNotification data structure, with accCtrlPolListExt in eventDetail parameter.<ol> <li>One Event should be ACCESS_CONTROL_POLICY_UPDATE with eventDetail with accCtrlPolListExt including the apiId and apiInvokerPolicies.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-11-provider-receives-an-acl-unavailable-event-when-invoker-remove-security-context","title":"Test Case 11: Provider receives an ACL unavailable event when invoker remove Security Context.","text":"<p>Test ID: capif_api_events-11, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to ACCESS_CONTROL_POLICY_UNAVAILABLE will receive the notification when AEF remove Security Context created previously.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>API Provider had one Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF.</li> <li>Publish a provider API with name service_1.</li> <li>Register Invoker and Onboard Invoker at CCF.</li> <li>Subscribe Invoker to ACCESS_CONTROL_POLICY_UNAVAILABLE event.</li> <li>Discover APIs filtered by aef_id</li> <li>Create Security Context for Invoker.</li> <li>Provider Retrieve ACL.</li> <li>Remove Security Context for Invoker.</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Perform invoker onboarding</li> <li>Event Subscription to ACCESS_CONTROL_POLICY_UNAVAILABLE event:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['ACCESS_CONTROL_POLICY_UNAVAILABLE']</li> <li>eventFilters: apiInvokerIds array with apiInvokerId of invoker</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li>Discover published APIs</li> <li>Create Security Context for Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li>Provider Retrieve ACL<ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li>Delete Security Context of Invoker by Provider:<ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Use AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain only one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> <li>Delete security context:<ol> <li>204 No Content response.</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>One Event has been received.</li> <li>Validate received event follow EventNotification data structure, without eventDetail parameter.<ol> <li>One Event should be ACCESS_CONTROL_POLICY_UNAVAILABLE without eventDetail.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-12-invoker-receives-an-invoker-authorization-revoked-and-acl-unavailable-event-when-provider-revoke-invoker-authorization","title":"Test Case 12: Invoker receives an Invoker Authorization Revoked and ACL unavailable event when Provider revoke Invoker Authorization.","text":"<p>Test ID: capif_api_events-12, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to API_INVOKER_AUTHORIZATION_REVOKED and ACCESS_CONTROL_POLICY_UNAVAILABLE receive both notification when AEF revoke invoker's authorization.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>API Provider had one Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF.</li> <li>Publish a provider API with name service_1.</li> <li>Register Invoker and Onboard Invoker at CCF.</li> <li>Subscribe Invoker to ACCESS_CONTROL_POLICY_UNAVAILABLE and API_INVOKER_AUTHORIZATION_REVOKED events.</li> <li>Discover APIs filtered by aef_id</li> <li>Create Security Context for Invoker.</li> <li>Revoke Authorization by Provider.</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Perform invoker onboarding</li> <li>Event Subscription to ACCESS_CONTROL_POLICY_UNAVAILABLE and API_INVOKER_AUTHORIZATION_REVOKED event:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['ACCESS_CONTROL_POLICY_UNAVAILABLE','API_INVOKER_AUTHORIZATION_REVOKED']</li> <li>eventFilters: apiInvokerIds array with apiInvokerId of invoker</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li>Discover published APIs</li> <li>Create Security Context for Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li>Revoke Authorization by Provider:<ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/delete</li> <li>body security notification body</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> <li>Revoke Authorization:<ol> <li>204 No Content response.</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>Two Events has been received.</li> <li>Validate received event follow EventNotification data structure, without eventDetail parameter.<ol> <li>One Event should be ACCESS_CONTROL_POLICY_UNAVAILABLE without eventDetail.</li> <li>One Event should be API_INVOKER_AUTHORIZATION_REVOKED without eventDetail.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/","title":"Test Plan for CAPIF Api Invoker Management","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_invoker_management/#test-case-1-onboard-network-app","title":"Test Case 1: Onboard Network App","text":"<p>Test ID: capif_api_invoker_management-1</p> <p>Description:</p> <p>This test will try to register new Network App at CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was not registered previously</li> <li>Network App was not onboarded previously</li> <li>Preconditions: The administrator must have previously registered the User.</li> </ul> <p>Execution Steps:</p> <ol> <li>Retrieve access_token by User from register</li> <li>Onboard Invoker at CCF</li> <li>Store signed Certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at invoker</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Onboard Invoker:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers</li> <li>Reference Request Body: invoker onboarding body</li> <li>\"onboardingInformation\"->\"apiInvokerPublicKey\": must contain public key generated by Invoker.</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-2-onboard-network-app-already-onboarded","title":"Test Case 2: Onboard Network App Already onboarded","text":"<p>Test ID: capif_api_invoker_management-2</p> <p>Description:</p> <p>This test will check second onboard of same Network App is not allowed.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Network App at CCF</li> <li>Onboard Network App at CCF</li> <li>Store signed Certificate at Network App</li> <li>Onboard Again the Network App at CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Repeat Onboard Invoker:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers</li> <li>Reference Request Body: invoker onboarding body</li> <li>\"onboardingInformation\"->\"apiInvokerPublicKey\": must contain public key generated by Invoker.</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Second Onboard of Network App must accomplish:<ol> <li>403 Forbidden</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 403</li> <li>title with message \"Forbidden\"</li> <li>detail with message \"Invoker Already registered\".</li> <li>cause with message \"Identical invoker public key\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-3-update-onboarded-network-app","title":"Test Case 3: Update Onboarded Network App","text":"<p>Test ID: capif_api_invoker_management-3</p> <p>Description:</p> <p>This test will try to update information of previous onboard Network App at CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Update Onboarding Information at CCF with a minor change on \"notificationDestination\"</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Update information of previously onboarded Invoker:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>\"notificationDestination\": \"http://host.docker.internal:8086/netapp_new_callback\",</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Update Request (PUT) with minor change must contain:<ol> <li>200 OK response.</li> <li>notificationDestination on response must contain the new value</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-4-update-not-onboarded-network-app","title":"Test Case 4: Update Not Onboarded Network App","text":"<p>Test ID: capif_api_invoker_management-4</p> <p>Description:</p> <p>This test will try to update information of not onboarded Network App at CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was not onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Update Onboarding Information at CCF of not onboarded</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Update information of not onboarded Invoker:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{INVOKER_NOT_REGISTERED}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> </ol> </li> <li>Response to Update Request (PUT) must contain:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Please provide an existing Network App ID\".</li> <li>cause with message \"Not exist Network App ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-5-offboard-network-app","title":"Test Case 5: Offboard Network App","text":"<p>Test ID: capif_api_invoker_management-5</p> <p>Description:</p> <p>This test case will check that a Registered Network App can be deleted.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Offboard Invoker at CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Offboard:</p> <ul> <li>Send DELETE to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> </ol> </li> <li>Response to Offboard Request (DELETE) must contain:<ol> <li>204 No Content</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-6-offboard-not-previsouly-onboarded-network-app","title":"Test Case 6: Offboard Not previsouly Onboarded Network App","text":"<p>Test ID: capif_api_invoker_management-6</p> <p>Description:</p> <p>This test case will check that a Non-Registered Network App cannot be deleted</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was not onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Offboard Invoker at CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Offboard:</p> <ul> <li>Send DELETE to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{INVOKER_NOT_REGISTERED}</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Offboard Request (DELETE) must contain:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Please provide an existing Network App ID\".</li> <li>cause with message \"Not exist Network App ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-7-update-onboarded-network-app-certificate","title":"Test Case 7: Update Onboarded Network App Certificate","text":"<p>Test ID: capif_api_invoker_management-7</p> <p>Description:</p> <p>This test will try to update public key and get a new signed certificate by CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId} and {public_key_1}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Update Onboarding Information at CCF with new public key</li> <li>Update Onboarding Information at CCF with minor change</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding with public_key_1.</p> </li> <li> <p>Create {public_key_2}</p> </li> <li> <p>Update information of previously onboarded Invoker:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>[\"onboardingInformation\"][\"apiInvokerPublicKey\"]: {public_key_2},</li> <li>Store new certificate.</li> </ul> </li> <li> <p>Update information of previously onboarded Invoker Using new certificate:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>\"notificationDestination\": \"http://host.docker.internal:8086/netapp_new_callback\",</li> <li>Use new Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Update Request (PUT) with new public key:<ol> <li>200 OK response.</li> <li>apiInvokerCertificate with new certificate on response -> store to use.</li> </ol> </li> <li>Response to Update Request (PUT) with minor change must contain:<ol> <li>200 OK response.</li> <li>notificationDestination on response must contain the new value</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/","title":"Test Plan for CAPIF Api Logging Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_logging_service/#test-case-1-creates-a-new-individual-capif-log-entry","title":"Test Case 1: Creates a new individual CAPIF Log Entry.","text":"<p>Test ID: capif_api_logging-1</p> <p>Description:</p> <p>This test case will check that a CAPIF AEF can create log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid aefId from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow InvocationLog data structure with:<ul> <li>aefId</li> <li>apiInvokerId</li> <li>logs</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invocation-logs/v1/{aefId}/logs/{logId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-2-creates-a-new-individual-capif-log-entry-with-invalid-aefid","title":"Test Case 2: Creates a new individual CAPIF Log Entry with Invalid aefId","text":"<p>Test ID: capif_api_logging-2</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is not pre-authorised (has not valid aefId from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{not-valid-aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Exposer not exist\".</li> <li>cause with message \"Exposer id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-3-creates-a-new-individual-capif-log-entry-with-invalid-serviceapi","title":"Test Case 3: Creates a new individual CAPIF Log Entry with Invalid serviceAPI","text":"<p>Test ID: capif_api_logging-3</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid aefId from CAPIF Authority)</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body [log entry request body with serviceAPI apiName apiId not valid]</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not exist\".</li> <li>cause with message \"Invoker id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-4-creates-a-new-individual-capif-log-entry-with-invalid-apiinvokerid","title":"Test Case 4: Creates a new individual CAPIF Log Entry with Invalid apiInvokerId","text":"<p>Test ID: capif_api_logging-4</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid aefId from CAPIF Authority)</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body [log entry request body with invokerId not valid]</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>For each apiProvFuncs, we must check:<ol> <li>apiProvFuncId is set</li> <li>apiProvCert under regInfo is set properly</li> </ol> </li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> <li> <p>Response to Logging Service must accomplish:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not exist\".</li> <li>cause with message \"Invoker id not found\".</li> </ul> </li> </ol> </li> <li> <p>Log Entry are not stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-5-creates-a-new-individual-capif-log-entry-with-invalid-aefid-in-body","title":"Test Case 5: Creates a new individual CAPIF Log Entry with Invalid aefId in body","text":"<p>Test ID: capif_api_logging-5</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId in body</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body [log entry request body with bad aefId] </li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"AEF id not matching in request and body\".</li> <li>cause with message \"Not identical AEF id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/","title":"Test Plan for CAPIF Api Provider Management","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_provider_management/#test-case-1-register-api-provider","title":"Test Case 1: Register Api Provider","text":"<p>Test ID: capif_api_provider_management-1</p> <p>Description:</p> <p>This test case will check that Api Provider can be registered con CCF</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid certificate from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Create private and public key for provider and each function to register.</li> <li>Register Provider.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Register Provider at Provider Management:<ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>For each apiProvFuncs, we must check:<ol> <li>apiProvFuncId is set</li> <li>apiProvCert under regInfo is set properly</li> </ol> </li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-2-register-api-provider-already-registered","title":"Test Case 2: Register Api Provider Already registered","text":"<p>Test ID: capif_api_provider_management-2</p> <p>Description:</p> <p>This test case will check that a Api Provider previously registered cannot be re-registered</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously and there is a {registerId} for his Api Provider in the DB</li> </ul> <p>Execution Steps:</p> <ol> <li>Create private and public key for provider and each function to register.</li> <li>Register Provider.</li> <li>Re-Register Provider.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Re-Register Provider:</p> <ul> <li>Same regSec than Previous registration</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Re-Register Provider:<ol> <li>403 Forbidden response.</li> <li> <p>body returned must accomplish ProblemDetails data structure, with:</p> <ul> <li>status 403</li> <li>title with message \"Forbidden\"</li> <li>detail with message \"Provider already registered\".</li> <li>cause with message \"Identical provider reg sec\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-3-update-registered-api-provider","title":"Test Case 3: Update Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-3</p> <p>Description:</p> <p>This test case will check that a Registered Api Provider can be updated</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously and there is a {registerId} for his Api Provider in the DB</li> </ul> <p>Execution Steps:</p> <ol> <li>Create private and public key for provider and each function to register.</li> <li>Register Provider</li> <li>Update Provider</li> </ol> <p>Information of Test:</p> <ol> <li>Create public and private key at provider for provider itself and each function (apf, aef and amf)</li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Get Resource URL from Location</li> </ul> </li> <li> <p>Update Provider:</p> <ul> <li>Send PUT to Resource URL returned at registration https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{registrationId}</li> <li>body provider request body with apiProvDomInfo set to ROBOT_TESTING_MOD</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Register Provider:</p> <ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> <li> <p>Update Provider:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure, with:<ul> <li>apiProvDomInfo set to ROBOT_TESTING_MOD</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-4-update-not-registered-api-provider","title":"Test Case 4: Update Not Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-4</p> <p>Description:</p> <p>This test case will check that a Non-Registered Api Provider cannot be updated</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was not registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Update Not Registered Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Update Not Registered Provider:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{API_PROVIDER_NOT_REGISTERED}</li> <li>body provider request body</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Update Not Registered Provider:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Not Exist Provider Enrolment Details\".</li> <li>cause with message \"Not found registrations to Send THIS api provider details\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-5-partially-update-registered-api-provider","title":"Test Case 5: Partially Update Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-5</p> <p>Description:</p> <p>This test case will check that a Registered Api Provider can be partially updated</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously and there is a {registerId} for his Api Provider in the DB</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Register Provider</li> <li>Partial update provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Partial update provider:</p> <ul> <li>Send PATCH https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{registrationId}</li> <li>body provider request patch body</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Partial update provider at Provider Management:<ol> <li>200 OK response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure, with:<ul> <li>apiProvDomInfo with \"ROBOT_TESTING_MOD\"</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-6-partially-update-not-registered-api-provider","title":"Test Case 6: Partially Update Not Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-6</p> <p>Description:</p> <p>This test case will check that a Non-Registered Api Provider cannot be partially updated </p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was not registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Register Provider</li> <li>Partial update provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Partial update Provider:</p> <ul> <li>Send PATCH https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{API_API_PROVIDER_NOT_REGISTERED}</li> <li>body provider request patch body</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Partial update provider:<ol> <li>404 Not Found response.</li> <li> <p>body returned must accomplish ProblemDetails data structure, with:</p> <ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Not Exist Provider Enrolment Details\".</li> <li>cause with message \"Not found registrations to Send THIS api provider details\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-7-delete-registered-api-provider","title":"Test Case 7: Delete Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-7</p> <p>Description:</p> <p>This test case will check that a Registered Api Provider can be deleted</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Register Provider</li> <li>Delete Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Delete registered provider:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{registrationId}</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete Provider:<ol> <li>204 No Content response.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-8-delete-not-registered-api-provider","title":"Test Case 8: Delete Not Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-8</p> <p>Description:</p> <p>This test case will check that a Non-Registered Api Provider cannot be deleted</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was not registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Delete Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Delete registered provider at Provider Management:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{API_PROVIDER_NOT_REGISTERED}</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete Provider:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Not Exist Provider Enrolment Details\".</li> <li>cause with message \"Not found registrations to Send THIS api provider details\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/","title":"Test Plan for CAPIF Api Publish Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_publish_service/#test-case-1-publish-api-by-authorised-api-publisher","title":"Test Case 1: Publish API by Authorised API Publisher","text":"<p>Test ID: capif_api_publish_service-1</p> <p>Description:</p> <p>This test case will check that an API Publisher can Publish an API</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li> <p>Register Provider at CCF and store certificates.</p> </li> <li> <p>Publish Service API</p> </li> <li> <p>Retrieve {apiId} from body and Location header with new resource created from response</p> </li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> </li> <li> <p>Send POST to ccf_publish_url: https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</p> </li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Published Service API is stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-2-publish-api-by-non-authorised-api-publisher","title":"Test Case 2: Publish API by NON Authorised API Publisher","text":"<p>Test ID: capif_api_publish_service-2</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Publish an API withot valid apfId </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API with invalid APF ID</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API with invalid APF ID at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{APF_ID_NOT_VALID}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Publisher not existing\".</li> <li>cause with message \"Publisher id not found\".</li> </ul> </li> </ol> </li> <li> <p>Service API is NOT stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-3-retrieve-all-apis-published-by-authorised-apfid","title":"Test Case 3: Retrieve all APIs Published by Authorised apfId","text":"<p>Test ID: capif_api_publish_service-3</p> <p>Description:</p> <p>This test case will check that an API Publisher can Retrieve all API published</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>At least 2 service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API service_1</li> <li>Retrieve {apiId1} from body and Location header with new resource created from response</li> <li>Publish Service API service_2</li> <li>Retrieve {apiId2} from body and Location header with new resource created from response</li> <li>Retrieve All published APIs and check if both are present.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Publish Other Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve all published APIs:</p> <ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to service 1 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId1}</li> </ol> </li> <li> <p>Response to service 2 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId2}</li> </ol> </li> <li> <p>Published Service APIs are stored in CAPIF Database</p> </li> <li> <p>Response to Retrieve all published APIs:</p> <ol> <li>200 OK</li> <li>Response body must return an array of ServiceAPIDescription data.</li> <li>Array must contain all previously published APIs.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-4-retrieve-all-apis-published-by-non-authorised-apfid","title":"Test Case 4: Retrieve all APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-4</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Retrieve API published when apfId is not authorised </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Retrieve All published APIs</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Retrieve all published APIs:</p> <ul> <li>Send GET to https://{CAPIF_HOSTNAME}/published-apis/v1/{APF_ID_NOT_VALID}/service-apis</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>401 Non Authorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Provider not existing\".</li> <li>cause with message \"Provider id not found\".</li> </ul> </li> </ol> </li> <li> <p>Service API is NOT stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-5-retrieve-single-apis-published-by-authorised-apfid","title":"Test Case 5: Retrieve single APIs Published by Authorised apfId","text":"<p>Test ID: capif_api_publish_service-5</p> <p>Description:</p> <p>This test case will check that an API Publisher can Retrieve API published one by one</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>At least 2 service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API service_1.</li> <li>Retrieve {apiId1} from body and Location header with new resource created from response.</li> <li>Publish Service API service_2.</li> <li>Retrieve {apiId2} from body and Location header with new resource created from response.</li> <li>Retrieve service_1 API Detail.</li> <li>Retrieve service_2 API Detail.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Publish Other Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve service_1 published APIs detail:</p> <ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{apiId1}</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve service_2 published APIs detail:</p> <ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{apiId2}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to service 1 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId1}</li> </ol> </li> <li> <p>Response to service 2 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId2}</li> </ol> </li> <li> <p>Published Service APIs are stored in CAPIF Database</p> </li> <li> <p>Response to Retrieve service_1 published API using apiId1:</p> <ol> <li>200 OK</li> <li>Response body must return a ServiceAPIDescription data.</li> <li>Array must contain same information than service_1 published registration response.</li> </ol> </li> <li> <p>Response to Retrieve service_2 published API using apiId2:</p> <ol> <li>200 OK</li> <li>Response body must return a ServiceAPIDescription data.</li> <li>Array must contain same information than service_2 published registration response.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-6-retrieve-single-apis-non-published-by-authorised-apfid","title":"Test Case 6: Retrieve single APIs non Published by Authorised apfId","text":"<p>Test ID: capif_api_publish_service-6</p> <p>Description:</p> <p>This test case will check that an API Publisher try to get detail of not published api.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>No published api</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Retrieve not published API Detail.</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration</li> <li>Retrieve not published APIs detail:<ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Retrieve for NOT published API must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"No Service with specific credentials exists\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-7-retrieve-single-apis-published-by-non-authorised-apfid","title":"Test Case 7: Retrieve single APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-7</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Retrieve detailed API published when apfId is not authorised </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API at CCF</li> <li>Retrieve {apiId} from body and Location header with new resource created from response.</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Invoker Certificate</li> <li>Retrieve detailed published API acting as Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve detailed published APIs:</p> <ul> <li>Send GET to https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/${apiId}</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Retrieve Detailed published API acting as Invoker must accomplish:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> <li> <p>Service API is NOT stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-8-update-api-published-by-authorised-apfid-with-valid-serviceapiid","title":"Test Case 8: Update API Published by Authorised apfId with valid serviceApiId","text":"<p>Test ID: capif_api_publish_service-8</p> <p>Description:</p> <p>This test case will check that an API Publisher can Update published API with a valid serviceApiId </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>A service APIs is published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API</li> <li>Retrieve {apiId} from body and Location header with new resource url created from response</li> <li>Update published Service API.</li> <li>Retrieve detail of Service API</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>get resource url from location Header.</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>body service api description with overrided apiName to service_1_modified</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve detail of service API:</p> <ul> <li>Send GET to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>check apiName is service_1_modified</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Update Published Service API:</p> <ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1_modified</li> </ul> </li> </ol> </li> <li> <p>Response to Retrieve detail of Service API:</p> <ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1_modified.</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-9-update-apis-published-by-authorised-apfid-with-invalid-serviceapiid","title":"Test Case 9: Update APIs Published by Authorised apfId with invalid serviceApiId","text":"<p>Test ID: capif_api_publish_service-9</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Update published API with a invalid serviceApiId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Update published Service API.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>body service api description with overrided apiName to service_1*_modified*</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Update Published Service API:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"Service API id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-10-update-apis-published-by-non-authorised-apfid","title":"Test Case 10: Update APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-10</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Update API published when apfId is not authorised</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API at CCF</li> <li>Retrieve {apiId} from body and Location header with new resource created from response.</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Invoker Certificate</li> <li>Update published API at CCF as Invoker</li> <li>Retrieve detail of Service API as publisher</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> <li>body service api description with overrided apiName to service_1*_modified*</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Retrieve detail of service API:</p> <ul> <li>Send GET to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>check apiName is service_1</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Update published API acting as Invoker must accomplish:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> <li> <p>Response to Retrieve Detail of Service API:</p> <ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1.</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-11-delete-api-published-by-authorised-apfid-with-valid-serviceapiid","title":"Test Case 11: Delete API Published by Authorised apfId with valid serviceApiId","text":"<p>Test ID: capif_api_publish_service-11</p> <p>Description:</p> <p>This test case will check that an API Publisher can Delete published API with a valid serviceApiId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority).</li> <li>A service APIs is published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API</li> <li>Retrieve {apiId} from body and Location header with new resource created from response</li> <li>Remove published API at CCF</li> <li>Try to retreive deleted service API from CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Remove published Service API at CCF:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> <li>Use APF Certificate</li> </ul> </li> <li>Retrieve detail of service API:<ul> <li>Send GET to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Published Service API is stored in CAPIF Database</p> </li> <li> <p>Response to Remove published Service API at CCF:</p> <ol> <li>204 No Content</li> </ol> </li> <li> <p>Response to Retrieve for DELETED published API must accomplish:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"No Service with specific credentials exists\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-12-delete-apis-published-by-authorised-apfid-with-invalid-serviceapiid","title":"Test Case 12: Delete APIs Published by Authorised apfId with invalid serviceApiId","text":"<p>Test ID: capif_api_publish_service-12</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Delete with invalid serviceApiId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority).</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Remove published API at CCF with invalid serviceId</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Remove published Service API at CCF with invalid serviceId:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Remove published Service API at CCF:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"Service API id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-13-delete-apis-published-by-non-authorised-apfid","title":"Test Case 13: Delete APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-12</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Delete API published when apfId is not authorised</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority).</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Register Invoker and onboard Invoker at CCF</li> <li>Remove published API at CCF with invalid serviceId as Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Remove published Service API at CCF with invalid serviceId as Invoker:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>Use Invoker Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Remove published Service API at CCF:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/","title":"Test Plan for CAPIF Api Security Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_security_service/#test-case-1-create-a-security-context-for-an-api-invoker","title":"Test Case 1: Create a security context for an API invoker","text":"<p>Test ID: capif_security_api-1</p> <p>Description:</p> <p>This test case will check that an API Invoker can create a Security context</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Invoker Onboarding</li> <li>Create Security Context for this Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-2-create-a-security-context-for-an-api-invoker-with-provider-role","title":"Test Case 2: Create a security context for an API invoker with Provider role","text":"<p>Test ID:: capif_security_api-2</p> <p>Description:</p> <p>This test case will check that an Provider cannot create a Security context with valid apiInvokerId.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID), but user that create Security Context with Provider role</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker but using Provider certificate.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Create security context using Provider certificate:</p> <ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\".</li> </ul> </li> </ol> </li> <li> <p>No context stored at DB</p> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-3-create-a-security-context-for-an-api-invoker-with-provider-entity-role-and-invalid-apiinvokerid","title":"Test Case 3: Create a security context for an API invoker with Provider entity role and invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-3</p> <p>Description:</p> <p>This test case will check that an Provider cannot create a Security context with invalid apiInvokerID.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID), but user that create Security Context with Provider role</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Create Security Context for this not valid apiInvokerId and using Provider certificate.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Create security context using Provider certificate:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\".</li> </ul> </li> </ol> </li> <li>No context stored at DB</li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-4-create-a-security-context-for-an-api-invoker-with-invoker-entity-role-and-invalid-apiinvokerid","title":"Test Case 4: Create a security context for an API invoker with Invoker entity role and invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-4</p> <p>Description:</p> <p>This test case will check that an Invoker cannot create a Security context with valid apiInvokerId.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID), but user that create Security Context with invalid apiInvokerId</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Create Security Context using Provider certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Create security context using Provider certificate:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> <li> <p>No context stored at DB</p> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-5-retrieve-the-security-context-of-an-api-invoker","title":"Test Case 5: Retrieve the Security Context of an API Invoker","text":"<p>Test ID:: capif_security_api-5</p> <p>Description:</p> <p>This test case will check that an provider can retrieve the Security context of an API Invoker</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Retrieve Security Context by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-6-retrieve-the-security-context-of-an-api-invoker-with-invalid-apiinvokerid","title":"Test Case 6: Retrieve the Security Context of an API Invoker with invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-6</p> <p>Description:</p> <p>This test case will check that an provider can retrieve the Security context of an API Invoker</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Retrieve Security Context by Provider of invalid invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Retrieve Security Context of invalid Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-7-retrieve-the-security-context-of-an-api-invoker-with-invalid-apfid","title":"Test Case 7: Retrieve the Security Context of an API Invoker with invalid apfId","text":"<p>Test ID:: capif_security_api-7</p> <p>Description:</p> <p>This test case will check that an Provider cannot retrieve the Security context of an API Invoker without valid apfId</p> <p>Pre-Conditions:</p> <ul> <li>API Exposure Function is not pre-authorised (has invalid apfId)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Retrieve Security Context as Provider.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Retrieve Security Context as Invoker role:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Create security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be aef\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-8-delete-the-security-context-of-an-api-invoker","title":"Test Case 8: Delete the Security Context of an API Invoker","text":"<p>Test ID:: capif_security_api-8</p> <p>Description:</p> <p>This test case will check that an Provider can delete a Security context</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Delete Security Context by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker but using Provider certificate.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> <li> <p>Delete Security Context of Invoker by Provider:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Delete security context:</p> <ol> <li>204 No Content response.</li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Security context not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-9-delete-the-security-context-of-an-api-invoker-with-invoker-entity-role","title":"Test Case 9: Delete the Security Context of an API Invoker with Invoker entity role","text":"<p>Test ID:: capif_security_api-9</p> <p>Description:</p> <p>This test case will check that an Invoker cannot delete a Security context</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Delete Security Context by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Delete Security Context of Invoker:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be aef\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-10-delete-the-security-context-of-an-api-invoker-with-invoker-entity-role-and-invalid-apiinvokerid","title":"Test Case 10: Delete the Security Context of an API Invoker with Invoker entity role and invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-10</p> <p>Description:</p> <p>This test case will check that an Invoker cannot delete a Security context with invalid </p> <p>Pre-Conditions:</p> <ul> <li>Invoker is pre-authorised.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Delete Security Context by invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Delete Security Context of Invoker:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be aef\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-11-delete-the-security-context-of-an-api-invoker-with-invalid-apiinvokerid","title":"Test Case 11: Delete the Security Context of an API Invoker with invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-11</p> <p>Description:</p> <p>This test case will check that an Provider cannot delete a Security context of invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority).</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Delete Security Context by provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Delete Security Context of Invoker by Provider:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>Use AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-12-update-the-security-context-of-an-api-invoker","title":"Test Case 12: Update the Security Context of an API Invoker","text":"<p>Test ID:: capif_security_api-12</p> <p>Description:</p> <p>This test case will check that an API Invoker can update a Security context</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context By Invoker</li> <li>Update Security Context By Invoker</li> <li>Retrieve Security Context By Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Update Security Context of Invoker:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/update</li> <li>body service security body but with notification destination modified to http://robot.testing2</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Update security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.<ol> <li>Check is this returned object match with modified one.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-13-update-the-security-context-of-an-api-invoker-with-provider-entity-role","title":"Test Case 13: Update the Security Context of an API Invoker with Provider entity role","text":"<p>Test ID:: capif_security_api-13</p> <p>Description:</p> <p>This test case will check that an Provider cannot update a Security context</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized.</li> <li>Invoker has created the Security Context previously.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context</li> <li>Update Security Context as Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Update Security Context of Invoker by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/update</li> <li>body service security body but with notification destination modified to http://robot.testing2</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Update security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\". </li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-14-update-the-security-context-of-an-api-invoker-with-aef-entity-role-and-invalid-apiinvokerid","title":"Test Case 14: Update the Security Context of an API Invoker with AEF entity role and invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-14</p> <p>Description:</p> <p>This test case will check that an Provider cannot update a Security context of invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized.</li> <li>Invoker has created the Security Context previously.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Update Security Context as Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Update Security Context of Invoker by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}/update</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Update security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\". </li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-15-update-the-security-context-of-an-api-invoker-with-invalid-apiinvokerid","title":"Test Case 15: Update the Security Context of an API Invoker with invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-15</p> <p>Description:</p> <p>This test case will check that an API Invoker cannot update a Security context not valid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Update Security Context</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Update Security Context of Invoker:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}/update</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-16-revoke-the-authorization-of-the-api-invoker-for-apis","title":"Test Case 16: Revoke the authorization of the API invoker for APIs.","text":"<p>Test ID:: capif_security_api-16</p> <p>Description:</p> <p>This test case will check that a Provider can revoke the authorization for APIs</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context by Invoker</li> <li>Revoke Security Context by Provider</li> <li>Retrieve Security Context by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context By Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Revoke Authorization by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/delete</li> <li>body security notification body</li> <li>Using AEF Certificate.</li> </ul> </li> <li> <p>Retrieve Security Context by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Revoke Authorization:</p> <ol> <li>204 No Content response.</li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Security context not found\".</li> <li>cause with message \"API Invoker has no security context\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-17-revoke-the-authorization-of-the-api-invoker-for-apis-without-valid-apfid","title":"Test Case 17: Revoke the authorization of the API invoker for APIs without valid apfID.","text":"<p>Test ID:: capif_security_api-17</p> <p>Description:</p> <p>This test case will check that an Invoker can't revoke the authorization for APIs</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context</li> <li>Revoke Security Context by invoker</li> <li>Retrieve Security Context</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Revoke Authorization by invoker:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/delete</li> <li>body security notification body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Revoke Security Context by invoker:</p> <ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be provider\". </li> </ul> </li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.<ol> <li>Check is this returned object match with created one.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-18-revoke-the-authorization-of-the-api-invoker-for-apis-with-invalid-apiinvokerid","title":"Test Case 18: Revoke the authorization of the API invoker for APIs with invalid apiInvokerId.","text":"<p>Test ID:: capif_security_api-18</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot revoke the authorization for APIs for invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context</li> <li>Revoke Security Context by Provider</li> <li>Retrieve Security Context</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Revoke Authorization by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}/delete</li> <li>body security notification body</li> <li>Using AEF Certificate.</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}?authenticationInfo=true&authorizationInfo=true</li> <li>This request will ask with parameter to retrieve authenticationInfo and authorizationInfo</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Revoke Security Context by invoker:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.<ol> <li>Check is this return one object that match with created one.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-19-retrieve-access-token","title":"Test Case 19: Retrieve access token","text":"<p>Test ID:: capif_security_api-19</p> <p>Description:</p> <p>This test case will check that an API Invoker can retrieve a security access token OAuth 2.0.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerId)</li> <li>Service API of Provider is published</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token:</li> <li>body access token req body and example example</li> <li>securityId is apiInvokerId.</li> <li>grant_type=client_credentials.</li> <li>Create Scope properly for request: 3gpp#{aef_id}:{api_name}</li> <li>Using Invoker Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>200 OK</li> <li>body must follow AccessTokenRsp with:<ol> <li>access_token present</li> <li>token_type=Bearer</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-20-retrieve-access-token-by-provider","title":"Test Case 20: Retrieve access token by Provider","text":"<p>Test ID:: capif_security_api-20</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot revoke the authorization for APIs for invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by provider:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token:</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error unauthorized_client</li> <li>error_description=Role not authorized for this API route</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-21-retrieve-access-token-by-provider-with-invalid-apiinvokerid","title":"Test Case 21: Retrieve access token by Provider with invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-21</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token without valid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by provider:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{API_INVOKER_NOT_VALID}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error unauthorized_client</li> <li>error_description=Role not authorized for this API route</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-22-retrieve-access-token-with-invalid-apiinvokerid","title":"Test Case 22: Retrieve access token with invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-22</p> <p>Description:</p> <p>This test case will check that an API Invoker can't retrieve a security access token without valid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerId)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs not filtered:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li>Create Security Context for this Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li>Request Access Token by invoker:<ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{API_INVOKER_NOT_VALID}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails29571 data structure, with:<ul> <li>status 404</li> <li>title Not Found</li> <li>detail Security context not found</li> <li>cause API Invoker has no security context</li> </ul> </li> </ol> </li> </ol> <p>NOTE: ProblemDetails29571 is the definition present for this request at swagger of ProblemDetails, and this is different from definition of ProblemDetails across other CAPIF Services</p>"},{"location":"testing/testplan/api_security_service/#test-case-23-retrieve-access-token-with-invalid-client_id","title":"Test Case 23: Retrieve access token with invalid client_id","text":"<p>Test ID:: capif_security_api-23</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token without valid client_id at body</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>client_id is not-valid </li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_client</li> <li>error_description=Client Id not found</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-24-retrieve-access-token-with-unsupported-grant_type","title":"Test Case 24: Retrieve access token with unsupported grant_type","text":"<p>Test ID:: capif_security_api-24</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with unsupported grant_type</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=not_valid</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error unsupported_grant_type</li> <li>error_description=Invalid value for <code>grant_type</code> \\(${grant_type}\\), must be one of \\['client_credentials'\\] - 'grant_type'</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-25-retrieve-access-token-with-invalid-scope","title":"Test Case 25: Retrieve access token with invalid scope","text":"<p>Test ID:: capif_security_api-25</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with complete invalid scope</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>scope=not-valid-scope</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_scope</li> <li>error_description=The first characters must be '3gpp'</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-26-retrieve-access-token-with-invalid-aefid-at-scope","title":"Test Case 26: Retrieve access token with invalid aefid at scope","text":"<p>Test ID:: capif_security_api-26</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with invalid aefId at scope</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>scope=3gpp#1234:*service_1*</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_scope</li> <li>error_description=One of aef_id not belongs of your security context</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-27-retrieve-access-token-with-invalid-apiname-at-scope","title":"Test Case 27: Retrieve access token with invalid apiName at scope","text":"<p>Test ID:: capif_security_api-27</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with invalid apiName at scope</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>scope=3gpp#{aef_id}:not-valid</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_scope</li> <li>error_description=One of the api names does not exist or is not associated with the aef id provided</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/common_operations/","title":"Common Operations","text":""},{"location":"testing/testplan/common_operations/#register-new-user","title":"Register new user","text":"<p>In order to use OpenCAPIF we must add a new user. This new user can onboard/register any Invokers or Providers.</p> <p>That new user must be created by administrator of Register Service and with the credentials shared by administrator, the new user can get the access_token by requesting it to Register service.</p> <p>The steps to register a new user at Register Service are:</p>"},{"location":"testing/testplan/common_operations/#admin-create-user","title":"Admin create User","text":"<p>1) Login as Admin to get access_token:</p> <ul> <li>Send POST to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/login<ul> <li>Include basic Auth Header with Admin credentials</li> </ul> </li> <li>Get access_token and refresh_token from response</li> </ul> <p></p> <p>2) Create User:</p> <ul> <li>Send POST to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/createUser<ul> <li>Include Admin access_token in Authorization Bearer Header</li> <li>Body user_registration_body</li> </ul> </li> </ul> <p></p>"},{"location":"testing/testplan/common_operations/#user-retrieve-access-token-and-other-information","title":"User Retrieve access token and other information","text":"<p>1) Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth<ul> <li>Include basic Auth Header with User credentials</li> </ul> </li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> <p></p>"},{"location":"testing/testplan/common_operations/#onboard-an-invoker","title":"Onboard an Invoker","text":""},{"location":"testing/testplan/common_operations/#steps-to-perform-operation","title":"Steps to perform operation","text":"<p>Preconditions: The administrator must have previously registered the User.</p> <ol> <li>Create public and private key at invoker</li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Onboard Invoker: </p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers</li> <li>Reference Request Body: invoker onboarding body</li> <li>\"onboardingInformation\"->\"apiInvokerPublicKey\": must contain public key generated by Invoker.</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> </ul> </li> </ol>"},{"location":"testing/testplan/common_operations/#checks-to-ensure-onboarding","title":"Checks to ensure onboarding","text":"<ol> <li> <p>Response to Get Auth:</p> <ol> <li>200 OK</li> <li>access_token returned.</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/common_operations/#example-flow","title":"Example Flow","text":""},{"location":"testing/testplan/common_operations/#register-a-provider","title":"Register a Provider","text":""},{"location":"testing/testplan/common_operations/#steps-to-perform-operation_1","title":"Steps to Perform operation","text":"<ol> <li>Create public and private key at provider for provider itself and each function (apf, aef and amf)</li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> <li>Store each cert in a file with according name.</li> </ul> </li> </ol>"},{"location":"testing/testplan/common_operations/#checks-to-ensure-provider-registration","title":"Checks to ensure provider registration","text":"<ol> <li> <p>Response to Register:</p> <ol> <li>201 Created</li> </ol> </li> <li> <p>Response to Get Auth:</p> <ol> <li>200 OK</li> <li>access_token returned.</li> </ol> </li> <li> <p>Register Provider at Provider Management:</p> <ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>For each apiProvFuncs, we must check:<ol> <li>apiProvFuncId is set</li> <li>apiProvCert under regInfo is set properly</li> </ol> </li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/common_operations/#example-flow_1","title":"Example Flow","text":""}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"<p>Welcome to the ETSI TeraFlowSDN (TFS) Controller wiki!</p> <p>This wiki provides a walkthrough on how to prepare your environment for executing and contributing to the ETSI SDG TeraFlowSDN. Besides, it describes how to run some example experiments.</p>"},{"location":"#try-teraflowsdn-release-30","title":"Try TeraFlowSDN Release 3.0","text":"<p>The new release launched on April 24th, 2024 incorporates a number of new features, improvements, and bug resolutions. Try it by following the guides below, and feel free to give us your feedback. See the Release Notes.</p>"},{"location":"#requisites","title":"Requisites","text":"<p>The guides and walkthroughs below make some reasonable assumptions to simplify the deployment of the TFS controller, the execution of experiments and tests, and the development of new contributions. In particular, we assume:</p> <ul> <li>A physical server or virtual machine for running the TFS controller with the following minimum specifications (check section Configure your Machine for additional details):</li> <li>4 cores / vCPUs</li> <li>8 GB of RAM (10 GB of RAM if you want to develop)</li> <li>60 GB of disk (100 GB of disk if you want to develop)</li> <li>1 NIC card</li> <li>VSCode with the Remote SSH extension</li> <li>Working machine software:</li> <li>Ubuntu Server 22.04.4 LTS or Ubuntu Server 20.04.6 LTS</li> <li>MicroK8s v1.24.17</li> </ul> <p>Use the Wiki menu in the right side of this page to navigate through the various contents of this wiki.</p>"},{"location":"#guides-and-walkthroughs","title":"Guides and Walkthroughs","text":"<p>The following guides and walkthroughs are provided:</p> <ul> <li>1. Deployment Guide</li> <li>2. Development Guide</li> <li>3. Run Experiments</li> <li>4. Features and Bugs</li> <li>5. Supported SBIs and Network Elements</li> <li>6. Supported NBIs</li> <li>7. Supported Service Handlers</li> <li>8. Troubleshooting</li> </ul>"},{"location":"#tutorials-and-tfs-virtual-machine","title":"Tutorials and TFS Virtual Machine","text":"<p>This section provides access to the links and all the materials prepared for the tutorials and hackfests involving ETSI TeraFlowSDN.</p> <ul> <li>TFS Hackfest #3 (Castelldefels, 16-17 October 2023)</li> <li> <p>The link includes explanatory material on P4 for TeraFlowSDN, the set of guided walkthrough, and the details on the interactive sessions the participants addressed (and recordings), as well as a TFS Virtual Machine (Release 2.1).</p> </li> <li> <p>TFS Hackfest #2 (Madrid, 20-21 June 2023)</p> </li> <li> <p>The link includes explanatory material on gNMI and ContainerLab for TeraFlowSDN, the set of challenges the participants addressed (and recordings), as well as a TFS Virtual Machine (Pre-Release 2.1).</p> </li> <li> <p>OFC SC472 (San Diego, 6 March 2023)</p> </li> <li> <p>The link includes a tutorial-style slide deck, as well as a TFS Virtual Machine (Release 2).</p> </li> <li> <p>TFS Hackfest #1 (Amsterdam, 20 October 2022)</p> </li> <li>The link includes a tutorial-style slide deck (and recordings), as well as a TFS Virtual Machine (Pre-Release 2).</li> </ul>"},{"location":"#versions","title":"Versions","text":"<p>New versions of TeraFlowSDN are periodically released. Each release is properly tagged and a branch is kept for its future bug fixing, if needed.</p> <ul> <li>The branch master, points always to the latest stable version of the TeraFlowSDN controller.</li> <li>The branches release/X.Y.Z, point to the code for the different release versions indicated in branch name.</li> <li>Code in these branches can be considered stable, and no new features are planned.</li> <li>In case of bugs, point releases increasing revision number (Z) might be created.</li> <li>The main development branch is named as develop.</li> <li>Use with care! Might not be stable.</li> <li>The latest developments and contributions are added to this branch for testing and validation before reaching a release. </li> </ul> <p>To choose the appropriate branch, follow the steps described in 1.3. Deploy TeraFlowSDN > Checkout the Appropriate Git Branch</p>"},{"location":"#events","title":"Events","text":"<p>Find here after the list of past and future TFS Events:</p> <ul> <li>ETSI TeraFlowSDN Events </li> </ul>"},{"location":"#contact","title":"Contact","text":"<p>If your environment does not fit with the proposed assumptions and you experience issues preparing it to work with the ETSI TeraFlowSDN controller, contact the ETSI TeraFlowSDN SDG team through Slack</p>"},{"location":"FAQ/","title":"Frequently Asked Questions (FAQ)","text":""},{"location":"FAQ/#does-the-user-have-to-develop-the-3-elements-of-the-provider-aef-amf-and-apf","title":"Does the user have to develop the 3 elements of the provider (AEF, AMF and APF)?","text":"<p>No, you only have to make the request to the \"/onboarding\" endpoint. In it you must specify a CSR for the AEF, APF and AMF and you will receive the certificates for each of them in the response.</p>"},{"location":"FAQ/#there-is-one-party-that-publishes-the-api-and-another-that-exposes-it-what-is-the-difference","title":"There is one party that publishes the API and another that exposes it, what is the difference?","text":"<p>There are different services, the APF, intended for publishing the APIs, and the AEF, intended so that the invoker can call it. The APF is what connects to the Capif Core Function to publish the service and when the service is up, you need the AEF service so that invokers can connect to it.</p>"},{"location":"FAQ/#before-publishing-an-api-do-you-have-to-be-registered-in-capif","title":"Before publishing an API, do you have to be registered in CAPIF?","text":"<p>Yes, before publishing an API you must register using the POST /register endpoint.</p>"},{"location":"FAQ/#where-is-the-registration-done","title":"Where is the registration done?","text":"<p>Registration is done in a REST API outside of the CAPIF specification taht we have implemented.</p>"},{"location":"FAQ/#is-the-username-and-password-chosen-by-the-user-when-registering-or-is-it-assigned-when-requesting-registration-to-capif-public-instance","title":"Is the username and password chosen by the user when registering or is it assigned when requesting registration to CAPIF public instance?","text":"<p>When you make the request to the \"/endpoint\" of register, you will be returned a username and a password determined by CAPIF.</p>"},{"location":"FAQ/#what-is-a-csr","title":"What is a CSR?","text":"<p>A CSR is a Certificate Signing Request. It is a generated data block where the certificate is planned to be installed and contains key information such as public key, organization, and location, and is used to request a certificate from a certificate authority (CA). In CAPIF, 3 CSRs are necessary to register a provider, for AEF, APF and AMF.</p>"},{"location":"FAQ/#when-doing-the-register_provider-where-can-i-find-the-csrs-that-are-generated","title":"When doing the register_provider where can I find the CSRs that are generated?","text":"<p>When using the \"register_provider\" command, if you add the \"debug\" option, it shows you a json with the data used to register the provider. There we can find in the body a list of 3 elements corresponding to AEF, APF and AMF. IN each of them, the apiProbPubKey field corresponds to the CSR.</p>"},{"location":"FAQ/#how-to-use-the-example-client-capif_invoker_gui","title":"How to use the example client (CAPIF_INVOKER_GUI)?","text":"<p>First you have to make a \"./run.sh host:port\" indicating the address of the public CAPIF. Once the Docker containers are up, you have to do a \"./terminal_to_py_netapp.sh\" and then a \"python main.py\". At this point we will find ourselves in a console with some predefined commands to use the Client. If we press tab twice it will bring up the list of available commands.</p>"},{"location":"FAQ/#where-is-the-capif-public-instance-located","title":"Where is the CAPIF public instance located?","text":"<p>The CAPIF public instance can be found at the following URLs: - capif.mobilesandbox.cloud:37211 (HTTPS) - capif.mobilesandbox.cloud:37212 (HTTP)</p>"},{"location":"FAQ/#do-you-have-to-publish-3-apis-one-for-each-instance","title":"Do you have to publish 3 APIs? one for each instance?","text":"<p>No, you only have to publish a single API but each component is responsible for a specific service, whether publishing or exposing.</p>"},{"location":"FAQ/#once-the-api-is-published-is-it-always-active-or-do-you-have-to-republish-it-every-time-you-want-to-use-it","title":"Once the API is published, is it always active? Or do you have to republish it every time you want to use it?","text":"<p>It is better to unsubscribe the API every time you exit the application since otherwise it could be republished and it would be double.</p>"},{"location":"FAQ/#would-the-same-username-and-password-be-valid-for-different-invokers","title":"Would the same username and password be valid for different invokers?","text":"<p>Yes, a user can have multiple invokers at the same time, and as such, the username and password would be the same.</p>"},{"location":"FAQ/#what-is-the-notfication-destination-field-in-the-register_invoker-request","title":"What is the notfication destination field in the register_invoker request?","text":"<p>This is the callback URL used to notify events. CAPIF has an Event service to subscribe to that notifies actions such as a subscription to an API, a change in the state of an API...</p>"},{"location":"FAQ/#is-the-notification_destination-a-required-field-in-the-register_invoker","title":"Is the notification_destination a required field in the register_invoker","text":"<p>No, it is not mandatory, but if you do not enter it you will not receive any CAPIF events. For example, the APF may delete the API, you will not be notified that the API is no longer available.</p>"},{"location":"FAQ/#what-is-the-purpose-of-the-discover_service-function-in-the-invoker-client","title":"What is the purpose of the \"discover_service\" function in the invoker client?","text":"<p>The discover_service returns a json with all the services that exist exposed in CAPIF at that moment.</p>"},{"location":"FAQ/#what-is-the-purpose-of-the-get_security_auth-function-in-the-invoker-client","title":"What is the purpose of the \"get_security_auth\" function in the invoker client?","text":"<p>Sirve para pedir el token o para refrescarlo en caso de que haya caducado. You have to use that token to call the API from the invoker.</p>"},{"location":"FAQ/#what-is-the-purpose-of-the-register_security_context-function-in-the-invoker-client","title":"What is the purpose of the \"register_security_context\" function in the invoker client?","text":"<p>To consume the API it is necessary to have a Security Context registered with the data and the authentication method.</p>"},{"location":"FAQ/#is-a-user-the-same-as-an-exposer","title":"Is a user the same as an exposer?","text":"<p>No, a user registers in CAPIF and once done can have the role of invoker, provider or both.</p>"},{"location":"FAQ/#where-can-i-put-my-endpoint","title":"Where can I put my endpoint?","text":"<p>You have to set your endpoint when doing the \"publish_service\" functionality: <code>publish_service capif_ops/config_files/service_api_description_hello.json</code></p> <p>In the file \"service_api_description_hello.json\" you configure the service that is going to be exposed and by developing one to suit you, you expose your API.</p>"},{"location":"architecture/","title":"Architecture","text":""},{"location":"architecture/#architecture","title":"Architecture","text":"<p>The CAPIF architecture has three main components, Register Service, Vault and CCF, which are represented in the following image:</p> <p></p> <p>Each component is separated into different namespaces and all communications between them use Rest APIs.</p> <p>Apart from the communication between components, there are 2 other entities that can use them:</p> <ul> <li>Admin/superadmin: Responsible for managing users with the Register or carrying out special operations in the CCF.</li> <li>Users: They are those who want to use CAPIF, registering as a user in the Register and as Invoker or Provider in the CCF.</li> </ul>"},{"location":"architecture/#register-ns","title":"Register NS","text":"<p>This namespace belongs to the Register service, and we find 2 components:</p> <ul> <li>Register Service: It is responsible for managing all users who use CAPIF, in addition to providing the necessary information for its use.</li> <li>Register MONGO DATABASE: It is the Register database, in it we store all the information about registered users.</li> </ul>"},{"location":"architecture/#vault-ns","title":"Vault NS","text":"<p>This namespace belongs to Vault. </p> <p>This component is responsible for managing all CAPIF certificates, so other components such as the Register or the CCF communicate with it to create new certificates or request keys.</p>"},{"location":"architecture/#mon-ns","title":"Mon NS","text":"<p>This is the main namespace of CAPIF, since it contains all the CCF services in addition to other components:</p> <ul> <li>NGINX: Responsible for acting as a reverse proxy to distribute CAPIF requests to the different services, controlling whether or not they are authorized to access them.</li> <li>REDIS: Used for internal communication of services.</li> <li>CAPIF MONGO DATABASE: CAPIF database, where all information related to CAPIF services such as invokers, registered providers or published services is stored.</li> <li>HELPER: Service that simplifies integration with third parties such as external management portals.</li> </ul>"},{"location":"architecture/#new-architecture","title":"New Architecture","text":"<p>You can check the details of all these changes in the conversation on the OCF wiki.</p>"},{"location":"releasenotes/","title":"Releasenotes","text":""},{"location":"releasenotes/#release-100","title":"Release 1.0.0","text":""},{"location":"releasenotes/#new-features","title":"New Features","text":""},{"location":"releasenotes/#registration-flow-improved","title":"Registration Flow improved","text":"<ul> <li>Eliminated access from CAPIF to the Register user database when onboarding is performed.</li> <li>Isolation between CCF and Register services, interaction now is only by HTTPS requested between Register, CCF and Vault.</li> <li>Eliminated the \"role\" in user creation.<ul> <li>Now a user can be an invoker or a provider at the same time</li> </ul> </li> <li>Administrator User:<ul> <li>New entity in charge of registering and managing users of the register service.</li> </ul> </li> <li>UUID to identify users.<ul> <li>When you create a user, a uuid is associated with it</li> <li>The uuid will be contained in the token requested by the user and will be used to relate invokers and providers with users.</li> </ul> </li> <li>Endpoints changed and created:<ul> <li>Administrator endpoints:<ul> <li>/createUser: /register endpoint changed to createUser. Used to register new users.</li> <li>/deleteUser: /remove endpoint changed to this. Used to delete users and all the entities they had created.</li> <li>/login: Allows administrator to log in to obtain the necessary tokens for their requests.</li> <li>/refresh: Retrieve new access token token.</li> <li>/getUsers: Returns the list with all registered users.</li> </ul> </li> <li>Customer User:<ul> <li>/getauth now also returns the urls needed to use CAPIF, used by customer.</li> </ul> </li> </ul> </li> <li> <p>Security improvements:</p> <ul> <li>/login uses basic auth with administrator credentials.</li> <li>/getauth uses basic auth with customer user credentials.</li> <li>Other requests use the administrator access token obtained from login.</li> </ul> </li> <li> <p>Current fields on user creation by administrator:</p> </li> </ul> <pre><code>required_fields = {\n \"username\": str,\n \"password\": str,\n \"enterprise\": str,\n \"country\": str,\n \"email\": str,\n \"purpose\": str\n}\n\noptional_fields = {\n \"phone_number\": str,\n \"company_web\": str,\n \"description\": str\n}\n</code></pre> <ul> <li>Test plan has been updated with the new register flow. Please check OCF Registration Flow</li> <li>Video with explanation and demonstration of new register flow New Registration Demo</li> </ul>"},{"location":"releasenotes/#new-opencapif-architecture","title":"New OpenCAPIF architecture","text":"<ul> <li>New arquitecture with separated namespaces for Vault, CCF and Register components. Communication between them now are only allowed by using REST APIs.</li> <li> <p>New helper service inside CCF, it will simplify integration with third parties like external management portals.</p> </li> <li> <p>Helper endpoints:</p> <ul> <li>/getInvokers : Get the list of invokers from CAPIF</li> <li>/getProviders: Get the list of providers from CAPIF</li> <li>/getServices : Get the list of services published in CAPIF</li> <li>/getSecurityContext : Get the list of security contexts from CAPIF</li> <li>/getEvents : Get the list of events subscriptions from CAPIF</li> <li>/deleteEntities: Removes all entities registered by a user from the register</li> </ul> </li> <li> <p>Security in the helper</p> <ul> <li>To make requests to the helper you will need a superadmin certificate and password.</li> </ul> </li> </ul>"},{"location":"releasenotes/#events-api-upgrade","title":"Events API Upgrade","text":"<ul> <li>The event management at CCF is improved, EventNotification include Event Details with required information.</li> <li>Events updated:<ul> <li>SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE with apiIds</li> <li>SERVICE_API_UPDATE with serviceAPIDescriptions</li> <li>API_INVOKER_ONBOARDED, API_INVOKER_UPDATED, API_INVOKER_OFFBOARDED with apiInvokerIds.</li> </ul> </li> <li>Events Included:<ul> <li>SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE with invocationLogs</li> </ul> </li> <li>Test plan include 7 new tests in order to check new events implemented and scenarios of each notification implemented, with a complete check of Event Notification.</li> <li>Test plan documentation includes the new event tests OCF Event test plan documentation.</li> </ul>"},{"location":"releasenotes/#inital-implementation-of-cicd","title":"Inital implementation of CI/CD","text":"<ul> <li>The inital implementation of CI/CD on gitlab was performed.</li> <li>Detailed information in the CICD Wiki.</li> <li>Implement initial CI/CD:<ul> <li>Description of the CI process.<ul> <li>In CI phase, created design, jobs and security checks when a branch is pushed.</li> <li>The CI has jobs as:<ul> <li>Linting code, unit test (if needed),</li> <li>Build and push artifacts (images) in Git OCI register</li> <li>Security checks,</li> <li>SCA, CVS, SAST</li> <li>The vulnerabilities are exposed in Merge Request panel to be solved.</li> </ul> </li> </ul> </li> <li>Description of the CD process:<ul> <li>Defined the environments to OCF.<ul> <li>Production env.</li> <li>Pre-production env.</li> <li>Validation env.</li> <li>Dev-1, dev-2\u2026 envs (ephemeral)</li> </ul> </li> <li>Defined the naming convention to OCF releases<ul> <li>Tag in prod: v0.0.1-release</li> <li>Tag non-prod: v0.0.1-rc</li> <li>Other tags: v0.0.1-test, v0.0.1-smt</li> </ul> </li> <li>Defined the jobs of CD<ul> <li>CD ensures the deployment in multiple envs. Therefore, the CD pipeline has deploy-ocf, delete-ocf (if needed) jobs</li> </ul> </li> </ul> </li> <li>ETSI HIVE Labs:<ul> <li>Designed, created and the Kuberntes OCF cluster is running to support OCFs deployments.</li> <li>Iterating with ETSI HIVE\u2019s support to solve computing issues.<ul> <li>CPU compatibilities with OCF services (MongoDB): Fixed</li> </ul> </li> </ul> </li> </ul> </li> </ul>"},{"location":"releasenotes/#documentation","title":"Documentation","text":""},{"location":"releasenotes/#improvements-on-documentation","title":"Improvements on documentation","text":"<ul> <li>Documentation stored in OCF Documentation Repository</li> <li>Continuous Integration included at repository for web documentation:<ul> <li>Develop version of documentation is automatically generated on each merge to develop branch.</li> <li>Tagged version from main create documentation with related tag as version.</li> </ul> </li> </ul>"},{"location":"releasenotes/#technical-debt-solved","title":"Technical Debt Solved","text":""},{"location":"releasenotes/#improved-testing-with-robot-in-order-to-cover","title":"Improved Testing with Robot in order to cover","text":"<ul> <li>Support of new Register flows.</li> <li>Allow different URLs for register, ccf and vault services.</li> <li>New Variables included to manage new architecture under test.</li> <li>Mock server developed to add the functionality of write tests involving notification from Service Under Test.</li> <li>Docker image improved generation and libraries upgraded to Robot Framework 7.</li> </ul>"},{"location":"releasenotes/#improved-security-on-db","title":"Improved security on DB","text":"<ul> <li>Credentials requested to access mongo databases.</li> <li>Credentials requested also by mongo-express.</li> </ul>"},{"location":"releasenotes/#scripts-upgraded","title":"Scripts upgraded","text":"<ul> <li>Docker compose version 2 used on them.</li> <li>New cleaning script developed.</li> <li>Scripts upgraded:<ul> <li>check_services_are_running.sh: Checks if all essential services (Vault, CCF and Register) are running.</li> <li>clean_capif_docker_services.sh: Shutdowns and removes all services essential services.</li> <li>clean_capif_temporary_files.sh: Removes temporaly files from local repository. </li> <li>run.sh: Launch Essential services locally using docker compose, also monitoring can be launched.</li> <li>run_capif_tests.sh: Launch Robot Framwork Tests.</li> <li>show_logs.sh: Show locally logs of Services running.</li> <li>run_mock_server.sh: Launch mock server locally on all interfaces. This axiliary server is only used by tagged mockserver tests on Robot Framework.</li> <li>clean_mock_server.sh: Remove mock server local deployment.</li> <li>deploy.sh: This script simplify the way to download capif repository.</li> </ul> </li> </ul>"},{"location":"releasenotes/#codebase-improvements","title":"Codebase Improvements","text":"<ul> <li>Documentation is now on splitted repository OCF Documentation Repository</li> <li>Test plan was moved to OCF Documentation Repository</li> <li>Obsolote data is removed.</li> <li>Repository Reorganization: Enhanced structure and maintainability with a better directory layout and clearer module separation.</li> <li>Code Quality Enhancements: Refactored code and fixed known issues</li> </ul>"},{"location":"releasenotes/#migration-to-gunicorn","title":"Migration to GUNICORN","text":"<ul> <li>Include production server on each microservice: Release 0 use Flask developer server, now we use GUNICORN.</li> </ul>"},{"location":"releasenotes/#release-00","title":"Release 0.0","text":"<p>The APIs included in Release 0.0 are:</p> <ul> <li>JWT Authentication APIs</li> <li>CAPIF Invoker Management API</li> <li>CAPIF Publish API</li> <li>CAPIF Discover API</li> <li>CAPIF Security API</li> <li>CAPIF Events API</li> <li>CAPIF Provider Management API</li> </ul> <p>This Release also includes a Robot Test Suite for all those services and a Postman Test Suite for simple testing.</p>"},{"location":"deployment_guide/deployment_guide/","title":"1. Deployment Guide","text":"<p>This section walks you through the process of deploying TeraFlowSDN on top of a machine running MicroK8s Kubernetes platform. The guide includes the details on configuring and installing the machine, installing and configuring MicroK8s, and deploying and reporting the status of the TeraFlowSDN controller.</p>"},{"location":"deployment_guide/deployment_guide/#11-configure-your-machine","title":"1.1. Configure your Machine","text":"<p>In this section, we describe how to configure a machine (physical or virtual) to be used as the deployment, execution, and development environment for the ETSI TeraFlowSDN controller. Choose your preferred environment below and follow the instructions provided.</p> <p>NOTE: If you already have a remote physical server fitting the requirements specified in this section feel free to use it instead of deploying a local VM. Check 1.1.1. Physical Server for further details.</p> <p>Virtualization platforms tested are:</p> <ul> <li>Physical Server</li> <li>Oracle Virtual Box</li> <li>VMWare Fusion</li> <li>OpenStack</li> <li>Vagrant Box</li> </ul>"},{"location":"deployment_guide/deployment_guide/#111-physical-server","title":"1.1.1. Physical ServerServer SpecificationsClusterized DeploymentNetworkingOperating SystemUpgrade the Ubuntu distribution","text":"<p>This section describes how to configure a physical server for running ETSI TeraFlowSDN(TFS) controller.</p> <p>Minimum Server Specifications for development and basic deployment</p> <ul> <li>CPU: 4 cores</li> <li>RAM: 8 GB</li> <li>Disk: 60 GB</li> <li>1 GbE NIC</li> </ul> <p>Recommended Server Specifications for development and basic deployment</p> <ul> <li>CPU: 6 cores</li> <li>RAM: 12 GB</li> <li>Disk: 80 GB</li> <li>1 GbE NIC</li> </ul> <p>Server Specifications for best development and deployment experience</p> <ul> <li>CPU: 8 cores</li> <li>RAM: 32 GB</li> <li>Disk: 120 GB</li> <li>1 GbE NIC</li> </ul> <p>NOTE: the specifications listed above are provided as a reference. They depend also on the CPU clock frequency, the RAM memory, the disk technology and speed, etc.</p> <p>For development purposes, it is recommended to run the VSCode IDE (or the IDE of your choice) in a more powerful server, for instance, the recommended server specifications for development and basic deployment.</p> <p>Given that TeraFlowSDN follows a micro-services architecture, for the deployment, it might be better to use many clusterized servers with many slower cores than a single server with few highly performant cores.</p> <p>You might consider creating a cluster of machines each featuring, at least, the minimum server specifications. That solution brings you scalability in the future.</p> <p>No explicit indications are given in terms of networking besides that servers need access to the Internet for downloading dependencies, binaries, and packages while building and deploying the TeraFlowSDN components.</p> <p>Besides that, the network requirements are essentially the same than that required for running a classical Kubernetes environment. To facilitate the deployment, we extensively use MicroK8s, thus the network requirements are, essentially, the same demanded by MicroK8s, especially, if you consider creating a Kubernetes cluster.</p> <p>As a reference, the other deployment solutions based on VMs assume the VM is connected to a virtual network configured with the IP range <code>10.0.2.0/24</code> and have the gateway at IP <code>10.0.2.1</code>. The VMs have the IP address <code>10.0.2.10</code>.</p> <p>The minimum required ports to be accessible are: - 22/SSH : for management purposes - 80/HTTP : for the TeraFlowSDN WebUI and Grafana dashboard - 8081/HTTPS : for the CockroachDB WebUI</p> <p>Other ports might be required if you consider to deploy addons such as Kubernetes observability, etc. The details on these ports are left appart given they might vary depending on the Kubernetes environment you use.</p> <p>The recommended Operating System for deploying TeraFlowSDN is Ubuntu Server 22.04 LTS or Ubuntu Server 20.04 LTS. Other version might work, but we have not tested them. We strongly recommend using Long Term Support (LTS) versions as they provide better stability.</p> <p>Below we provide some installation guidelines: - Installation Language: English - Autodetect your keyboard - If asked, select \"Ubuntu Server\" (do not select \"Ubuntu Server (minimized)\"). - Configure static network specifications (adapt them based on your particular setup):</p> Interface IPv4 Method Subnet Address Gateway Name servers Search domains enp0s3 Manual 10.0.2.0/24 10.0.2.10 10.0.2.1 8.8.8.8,8.8.4.4 <ul> <li>Leave proxy and mirror addresses as they are</li> <li>Let the installer self-upgrade (if asked).</li> <li>Use an entire disk for the installation</li> <li>Disable setup of the disk as LVM group</li> <li>Double check that NO swap space is allocated in the partition table. Kubernetes does not work properly with SWAP.</li> <li>Configure your user and system names:</li> <li>User name: <code>TeraFlowSDN</code></li> <li>Server's name: <code>tfs-vm</code></li> <li>Username: <code>tfs</code></li> <li>Password: <code>tfs123</code></li> <li>Install Open SSH Server</li> <li>Import SSH keys, if any.</li> <li>Featured Server Snaps</li> <li>Do not install featured server snaps. It will be done manually later to illustrate how to uninstall and reinstall them in case of trouble with.</li> <li>Let the system install and upgrade the packages.</li> <li>This operation might take some minutes depending on how old is the Optical Drive ISO image you use and your Internet connection speed.</li> <li>Restart the VM when the installation is completed.</li> </ul> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul>"},{"location":"deployment_guide/deployment_guide/#112-oracle-virtual-box","title":"1.1.2. Oracle Virtual BoxCreate a NAT Network in VirtualBoxCreate VM in VirtualBox:Install Ubuntu 22.04 LTS Operating System","text":"<p>This section describes how to configure a VM for running ETSI TeraFlowSDN(TFS) controller using Oracle VirtualBox. It has been tested with VirtualBox up to version 6.1.40 r154048.</p> <p>In \"Oracle VM VirtualBox Manager\", Menu \"File > Preferences... > Network\", create a NAT network with the following specifications:</p> Name CIDR DHCP IPv6 TFS-NAT-Net 10.0.2.0/24 Disabled Disabled <p>Within the newly created \"TFS-NAT-Net\" NAT network, configure the following IPv4 forwarding rules:</p> Name Protocol Host IP Host Port Guest IP Guest Port SSH TCP 127.0.0.1 2200 10.0.2.10 22 HTTP TCP 127.0.0.1 8080 10.0.2.10 80 <p>Note: IP address 10.0.2.10 is the one that will be assigned to the VM.</p> <ul> <li>Name: TFS-VM</li> <li>Type/Version: Linux / Ubuntu (64-bit)</li> <li>CPU (*): 4 vCPUs @ 100% execution capacity</li> <li>RAM: 8 GB</li> <li>Disk: 60 GB, Virtual Disk Image (VDI), Dynamically allocated</li> <li>Optical Drive ISO Image: \"ubuntu-22.04.X-live-server-amd64.iso\"</li> <li>Download the latest Long Term Support (LTS) version of the Ubuntu Server image from Ubuntu 22.04 LTS, e.g., \"ubuntu-22.04.X-live-server-amd64.iso\".</li> <li>Note: use Ubuntu Server image instead of Ubuntu Desktop to create a lightweight VM.</li> <li>Network Adapter 1 (*): enabled, attached to NAT Network \"TFS-NAT-Net\"</li> <li>Minor adjustments (*):</li> <li>Audio: disabled</li> <li>Boot order: disable \"Floppy\"</li> </ul> <p>Note: (*) settings to be editing after the VM is created.</p> <p>In \"Oracle VM VirtualBox Manager\", start the VM in normal mode, and follow the installation procedure. Below we provide some installation guidelines: - Installation Language: English - Autodetect your keyboard - If asked, select \"Ubuntu Server\" (do not select \"Ubuntu Server (minimized)\"). - Configure static network specifications:</p> Interface IPv4 Method Subnet Address Gateway Name servers Search domains enp0s3 Manual 10.0.2.0/24 10.0.2.10 10.0.2.1 8.8.8.8,8.8.4.4 <ul> <li>Leave proxy and mirror addresses as they are</li> <li>Let the installer self-upgrade (if asked).</li> <li>Use an entire disk for the installation</li> <li>Disable setup of the disk as LVM group</li> <li>Double check that NO swap space is allocated in the partition table. Kubernetes does not work properly with SWAP.</li> <li>Configure your user and system names:</li> <li>User name: TeraFlowSDN</li> <li>Server's name: tfs-vm</li> <li>Username: tfs</li> <li>Password: tfs123</li> <li>Install Open SSH Server</li> <li>Import SSH keys, if any.</li> <li>Featured Server Snaps</li> <li>Do not install featured server snaps. It will be done manually later to illustrate how to uninstall and reinstall them in case of trouble with.</li> <li>Let the system install and upgrade the packages.</li> <li>This operation might take some minutes depending on how old is the Optical Drive ISO image you use and your Internet connection speed.</li> <li>Restart the VM when the installation is completed.</li> </ul> <p>Upgrade the Ubuntu distribution</p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul> <p>Install VirtualBox Guest Additions On VirtualBox Manager, open the VM main screen. If you are running the VM in headless mode, right click over the VM in the VirtualBox Manager window and click \"Show\". If a dialog informing about how to leave the interface of the VM is shown, confirm pressing \"Switch\" button. The interface of the VM should appear.</p> <p>Click menu \"Device > Insert Guest Additions CD image...\"</p> <p>On the VM terminal, type:</p> <pre><code>sudo apt-get install -y linux-headers-$(uname -r) build-essential dkms\n # This command might take some minutes depending on your VM specs and your Internet access speed.\nsudo mount /dev/cdrom /mnt/\ncd /mnt/\nsudo ./VBoxLinuxAdditions.run\n # This command might take some minutes depending on your VM specs.\nsudo reboot\n</code></pre>"},{"location":"deployment_guide/deployment_guide/#113-vmware-fusion","title":"1.1.3. VMWare FusionCreate VM in VMWare Fusion:Install Ubuntu 22.04.1 LTS Operating SystemUpgrade the Ubuntu distribution","text":"<p>This section describes how to configure a VM for running ETSI TeraFlowSDN(TFS) controller using VMWare Fusion. It has been tested with VMWare Fusion version 12 and 13.</p> <p>In \"VMWare Fusion\" manager, create a new network from the \"Settings/Network\" menu.</p> <ul> <li>Unlock to make changes</li> <li>Press the + icon and create a new network</li> <li>Change the name to TFS-NAT-Net</li> <li>Check \"Allow virtual machines on this network to connect to external network (NAT)\"</li> <li>Do not check \"Enable IPv6\"</li> <li>Add port forwarding for HTTP and SSH</li> <li>Uncheck \"Provide address on this network via DHCP\"</li> </ul> <p>Create a new VM an Ubuntu 22.04.1 ISO:</p> <ul> <li>Display Name: TeraFlowSDN</li> <li>Username: tfs</li> <li>Password: tfs123</li> </ul> <p>On the next screen press \"Customize Settings\", save the VM and in \"Settings\" change: - Change to use 4 CPUs - Change to access 8 GB of RAM - Change disk to size 60 GB - Change the network interface to use the previously created TFS-NAT-Net</p> <p>Run the VM to start the installation.</p> <p>The installation will be automatic, without any configuration required.</p> <ul> <li>Configure the guest IP, gateway and DNS:</li> </ul> <p>Using the Network Settings for the wired connection, set the IP to 10.0.2.10, the mask to 255.255.255.0, the gateway to 10.0.2.2 and the DNS to 10.0.2.2.</p> <ul> <li>Disable and remove swap file:</li> </ul> <p>$ sudo swapoff -a $ sudo rm /swapfile</p> <p>Then you can remove or comment the /swapfile entry in /etc/fstab</p> <ul> <li>Install Open SSH Server</li> <li> <p>Import SSH keys, if any.</p> </li> <li> <p>Restart the VM when the installation is completed.</p> </li> </ul> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre>"},{"location":"deployment_guide/deployment_guide/#114-openstack","title":"1.1.4. OpenStackCreate a Security Group in OpenStack <p> In OpenStack, go to Project - Network - Security Groups - Create Security Group with name TFS</p> <p>Add the following rules:</p> Direction Ether Type IP Protocol Port Range Remote IP Prefix Ingress IPv4 TCP 22 (SSH) 0.0.0.0/0 Ingress IPv4 TCP 2200 0.0.0.0/0 Ingress IPv4 TCP 8080 0.0.0.0/0 Ingress IPv4 TCP 80 0.0.0.0/0 Egress IPv4 Any Any 0.0.0.0/0 Egress IPv6 Any Any ::/0 <p>Note: The IP address will be assigned depending on the network you have configured inside OpenStack. This IP will have to be modified in TeraFlow configuration files which by default use IP 10.0.2.10</p> Create a flavour <p></p> <p>From dashboard (Horizon)</p> <p>Go to Admin - Compute - Flavors and press Create Flavor</p> <ul> <li>Name: TFS</li> <li>VCPUs: 4</li> <li>RAM (MB): 8192</li> <li>Root Disk (GB): 60</li> </ul> <p>From CLI</p> <pre><code> openstack flavor create TFS --id auto --ram 8192 --disk 60 --vcpus 8\n</code></pre> Create an instance in OpenStack: <p></p> <ul> <li>Instance name: TFS-VM</li> <li>Origin: [Ubuntu-22.04 cloud image] (https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img)</li> <li>Create new volume: No</li> <li>Flavor: TFS</li> <li>Networks: extnet </li> <li>Security Groups: TFS</li> <li>Configuration: Include the following cloud-config</li> </ul> <pre><code>#cloud-config\n# Modifies the password for the VM instance\nusername: ubuntu\npassword: <your-password>\nchpasswd: { expire: False }\nssh_pwauth: True\n</code></pre> Upgrade the Ubuntu distribution <p></p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul>","text":"<p>This section describes how to configure a VM for running ETSI TeraFlowSDN(TFS) controller using OpenStack. It has been tested with OpenStack Kolla up to Yoga version. </p>"},{"location":"deployment_guide/deployment_guide/#115-vagrant-box","title":"1.1.5. Vagrant Box <p>This section describes how to create a Vagrant Box, using the base virtual machine configured in Oracle Virtual Box.</p> Virtual Machine specifications <p> Most of the specifications can be as specified in the Oracle Virtual Box page, however, there are a few particularities to Vagrant that must be accommodated, such as:</p> <ul> <li>Virtual Hard Disk</li> <li>Size: 60GB (at least)</li> <li>Type: VMDK</li> </ul> <p></p> <p>Also, before initiating the VM and installing the OS, we'll need to:</p> <ul> <li>Disable Floppy in the 'Boot Order'</li> <li>Disable audio</li> <li>Disable USB</li> <li>Ensure Network Adapter 1 is set to NAT</li> </ul> Network configurations <p> At Network Adapt 1, the following port-forwarding rule must be set.</p> Name Protocol Host IP Host Port Guest IP Guest Port SSH TCP 2222 22 <p></p> Installing the OS <p></p> <p>For a Vagrant Box, it is generally suggested that the ISO's server version is used, as it is intended to be used via SSH, and any web GUI is expected to be forwarded to the host.</p> <p></p> <p></p> <p></p> <p>Make sure the disk is not configured as an LVM group!</p> <p></p> Vagrant ser <p> Vagrant expects by default, that in the box's OS exists the user <code>vagrant</code> with the password also being <code>vagrant</code>.</p> <p></p> SSH <p></p> <p>Vagrant uses SSH to connect to the boxes, so installing it now will save the hassle of doing it later.</p> <p></p> Features server snaps <p></p> <p>Do not install featured server snaps. It will be done manually later to illustrate how to uninstall and reinstall them in case of trouble with.</p> Updates <p></p> <p>Let the system install and upgrade the packages. This operation might take some minutes depending on how old is the Optical Drive ISO image you use and your Internet connection speed.</p> Upgrade the Ubuntu distribution <p></p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> <ul> <li>If asked to restart services, restart the default ones proposed.</li> <li>Restart the VM when the installation is completed.</li> </ul> Install VirtualBox Guest Additions <p> On VirtualBox Manager, open the VM main screen. If you are running the VM in headless mode, right-click over the VM in the VirtualBox Manager window, and click \"Show\". If a dialog informing about how to leave the interface of the VM is shown, confirm by pressing the \"Switch\" button. The interface of the VM should appear.</p> <p>Click the menu \"Device > Insert Guest Additions CD image...\"</p> <p>On the VM terminal, type:</p> <pre><code>sudo apt-get install -y linux-headers-$(uname -r) build-essential dkms\n # This command might take some minutes depending on your VM specs and your Internet access speed.\nsudo mount /dev/cdrom /mnt/\ncd /mnt/\nsudo ./VBoxLinuxAdditions.run\n # This command might take some minutes depending on your VM specs.\nsudo reboot\n</code></pre> ETSI TFS Installation <p> After this, proceed to 1.2. Install Microk8s, after which, return to this wiki to finish the Vagrant Box creation.</p> Box configuration and creation <p> Make sure the ETSI TFS controller is correctly configured. You will not be able to change it after!</p> <p>It is advisable to do the next configurations from a host's terminal, via a SSH connection.</p> <pre><code>ssh -p 2222 vagrant@127.0.0.1\n</code></pre> Set root password <p> Set the root password to <code>vagrant</code>.</p> <pre><code>sudo passwd root\n</code></pre> Set the superuser <p> Set up the Vagrant user so that it\u2019s able to use sudo without being prompted for a password. Anything in the <code>/etc/sudoers.d/*</code> directory is included in the sudoers privileges when created by the root user. Create a new sudo file.</p> <pre><code>sudo visudo -f /etc/sudoers.d/vagrant\n</code></pre> <p>and add the following lines</p> <pre><code># add vagrant user\nvagrant ALL=(ALL) NOPASSWD:ALL\n</code></pre> <p>You can now test that it works by running a simple command.</p> <pre><code>sudo pwd\n</code></pre> <p>Issuing this command should result in an immediate response without a request for a password.</p> Install the Vagrant key <p> Vagrant uses a default set of SSH keys for you to directly connect to boxes via the CLI command <code>vagrant ssh</code>, after which it creates a new set of SSH keys for your new box. Because of this, we need to load the default key to be able to access the box after created.</p> <pre><code>chmod 0700 /home/vagrant/.ssh\nwget --no-check-certificate https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub -O /home/vagrant/.ssh/authorized_keys\nchmod 0600 /home/vagrant/.ssh/authorized_keys\nchown -R vagrant /home/vagrant/.ssh\n</code></pre> Configure the OpenSSH Server <p> Edit the <code>/etc/ssh/sshd_config</code> file:</p> <pre><code>sudo vim /etc/ssh/sshd_config\n</code></pre> <p>And uncomment the following line:</p> <pre><code>AuthorizedKeysFile %h/.ssh/authorized_keys\n</code></pre> <p>Then restart SSH.</p> <pre><code>sudo service ssh restart\n</code></pre> Package the box <p> Before you package the box, if you intend to make your box public, it is best to clean your bash history with:</p> <pre><code>history -c\n</code></pre> <p>Exit the SSH connection, and at you're host machine, package the VM:</p> <pre><code>vagrant package --base teraflowsdncontroller --output teraflowsdncontroller.box\n</code></pre> Test run the box <p> Add the base box to you local Vagrant box list:</p> <pre><code>vagrant box add --name teraflowsdncontroller ./teraflowsdncontroller.box\n</code></pre> <p>Now you should try to run it, for that you'll need to create a Vagrantfile. For a simple run, this is the minimal required code for this box:</p> <pre><code># -*- mode: ruby -*-\n# vi: set ft=ruby :\n\nVagrant.configure(\"2\") do |config|\n config.vm.box = \"teraflowsdncontroller\"\n config.vm.box_version = \"1.1.0\"\n config.vm.network :forwarded_port, host: 8080 ,guest: 80\nend\n</code></pre> <p>Now you'll be able to spin up the virtual machine by issuing the command:</p> <pre><code>vagrant up\n</code></pre> <p>And connect to the machine using:</p> <pre><code>vagrant ssh\n</code></pre> Pre-configured boxes <p> If you do not wish to create your own Vagrant Box, you can use one of the existing ones created by TFS contributors. - davidjosearaujo/teraflowsdncontroller - ... </p> <p>To use them, you simply have to create a Vagrantfile and run <code>vagrant up controller</code> in the same directory. The following example Vagrantfile already allows you to do just that, with the bonus of exposing the multiple management GUIs to your <code>localhost</code>.</p> <pre><code>Vagrant.configure(\"2\") do |config|\n\n config.vm.define \"controller\" do |controller|\n controller.vm.box = \"davidjosearaujo/teraflowsdncontroller\"\n controller.vm.network \"forwarded_port\", guest: 80, host: 8080 # WebUI\n controller.vm.network \"forwarded_port\", guest: 8084, host: 50750 # Linkerd Viz Dashboard\n controller.vm.network \"forwarded_port\", guest: 8081, host: 8081 # CockroachDB Dashboard\n controller.vm.network \"forwarded_port\", guest: 8222, host: 8222 # NATS Dashboard\n controller.vm.network \"forwarded_port\", guest: 9000, host: 9000 # QuestDB Dashboard\n controller.vm.network \"forwarded_port\", guest: 9090, host: 9090 # Prometheus Dashboard\n\n # Setup Linkerd Viz reverse proxy\n ## Copy config file\n controller.vm.provision \"file\" do |f|\n f.source = \"./reverse-proxy-linkerdviz.sh\"\n f.destination = \"./reverse-proxy-linkerdviz.sh\"\n end\n ## Execute configuration file\n controller.vm.provision \"shell\" do |s|\n s.inline = \"chmod +x ./reverse-proxy-linkerdviz.sh && ./reverse-proxy-linkerdviz.sh\"\n end\n\n # Update controller source code to the desired branch\n if ENV['BRANCH'] != nil\n controller.vm.provision \"shell\" do |s|\n s.inline = \"cd ./tfs-ctrl && git pull && git switch \" + ENV['BRANCH']\n end\n end\n\n end\nend\n</code></pre> <p>This Vagrantfile also allows for optional repository updates on startup by running the command with a specified environment variable <code>BRANCH</code></p> <pre><code>BRANCH=develop vagrant up controller\n</code></pre> Linkerd DNS rebinding bypass <p> Because of Linkerd's security measures against DNS rebinding, a reverse proxy, that modifies the request's header <code>Host</code> field, is needed to expose the GUI to the host. The previous Vagrantfile already deploys such configurations, for that, all you need to do is create the <code>reverse-proxy-linkerdviz.sh</code> file in the same directory. The content of this file is displayed below.</p> <pre><code># Install NGINX\nsudo apt update && sudo apt install nginx -y\n\n# NGINX reverse proxy configuration\necho 'server {\n listen 8084;\n\n location / {\n proxy_pass http://127.0.0.1:50750;\n proxy_set_header Host localhost;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n }\n}' > /home/vagrant/expose-linkerd\n\n# Create symlink of the NGINX configuration file\nsudo ln -s /home/vagrant/expose-linkerd /etc/nginx/sites-enabled/\n\n# Commit the reverse proxy configurations\nsudo systemctl restart nginx\n\n# Enable start on login\necho \"linkerd viz dashboard &\" >> .profile\n\n# Start dashboard\nlinkerd viz dashboard &\n\necho \"Linkerd Viz dashboard running!\"\n</code></pre>","text":""},{"location":"deployment_guide/deployment_guide/#12-install-microk8s","title":"1.2. Install MicroK8s","text":"<p>This section describes how to deploy the MicroK8s Kubernetes platform and configure it to be used with ETSI TeraFlowSDN controller. Besides, Docker is installed to build docker images for the ETSI TeraFlowSDN controller.</p> <p>The steps described in this section might take some minutes depending on your internet connection speed and the resources assigned to your VM, or the specifications of your physical server.</p> <p>To facilitate work, these steps are easier to be executed through an SSH connection, for instance using tools like PuTTY or MobaXterm.</p> Upgrade the Ubuntu distribution <p> Skip this step if you already did it during the creation of the VM.</p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> Install prerequisites <p></p> <pre><code>sudo apt-get install -y ca-certificates curl gnupg lsb-release snapd jq\n</code></pre> Install Docker CE <p> Install Docker CE and Docker BuildX plugin</p> <pre><code>sudo apt-get install -y docker.io docker-buildx\n</code></pre> <p>NOTE: Starting from Docker v23, Build architecture has been updated and <code>docker build</code> command entered into deprecation process in favor of the new <code>docker buildx build</code> command. Package <code>docker-buildx</code> provides the new <code>docker buildx build</code> command.</p> <p>Add key \"insecure-registries\" with the private repository to the daemon configuration. It is done in two commands since sometimes read from and write to same file might cause trouble.</p> <pre><code>if [ -s /etc/docker/daemon.json ]; then cat /etc/docker/daemon.json; else echo '{}'; fi \\\n | jq 'if has(\"insecure-registries\") then . else .+ {\"insecure-registries\": []} end' -- \\\n | jq '.\"insecure-registries\" |= (.+ [\"localhost:32000\"] | unique)' -- \\\n | tee tmp.daemon.json\nsudo mv tmp.daemon.json /etc/docker/daemon.json\nsudo chown root:root /etc/docker/daemon.json\nsudo chmod 600 /etc/docker/daemon.json\n</code></pre> <p>Restart the Docker daemon</p> <pre><code>sudo systemctl restart docker\n</code></pre> Install MicroK8s <p></p> <p>Important: Some TeraFlowSDN dependencies need to be executed on top of MicroK8s/Kubernetes v1.24. It is not guaranteed (by now) to run on newer versions.</p> <pre><code># Install MicroK8s\nsudo snap install microk8s --classic --channel=1.24/stable\n\n# Create alias for command \"microk8s.kubectl\" to be usable as \"kubectl\"\nsudo snap alias microk8s.kubectl kubectl\n</code></pre> <p>It is important to make sure that <code>ufw</code> will not interfere with the internal pod-to-pod and pod-to-Internet traffic. To do so, first check the status. If <code>ufw</code> is active, use the following command to enable the communication.</p> <pre><code>\n# Verify status of ufw firewall\nsudo ufw status\n\n# If ufw is active, install following rules to enable access pod-to-pod and pod-to-internet\nsudo ufw allow in on cni0 && sudo ufw allow out on cni0\nsudo ufw default allow routed\n</code></pre> <p>NOTE: MicroK8s can be used to compose a Highly Available Kubernetes cluster enabling you to construct an environment combining the CPU, RAM and storage resources of multiple machines. If you are interested in this procedure, review the official instructions in How to build a highly available Kubernetes cluster with MicroK8s, in particular, the step Create a MicroK8s multi-node cluster.</p> <p>References:</p> <ul> <li>The lightweight Kubernetes > Install MicroK8s</li> <li>Install a local Kubernetes with MicroK8s</li> <li>How to build a highly available Kubernetes cluster with MicroK8s</li> </ul> Add user to the docker and microk8s groups <p></p> <p>It is important that your user has the permission to run <code>docker</code> and <code>microk8s</code> in the terminal. To allow this, you need to add your user to the <code>docker</code> and <code>microk8s</code> groups with the following commands:</p> <pre><code>sudo usermod -a -G docker $USER\nsudo usermod -a -G microk8s $USER\nsudo chown -f -R $USER $HOME/.kube\nsudo reboot\n</code></pre> <p>In case that you get trouble executing the following commands, might due to the .kube folder is not automatically provisioned into your home folder, you may follow the steps below:</p> <pre><code>mkdir -p $HOME/.kube\nsudo chown -f -R $USER $HOME/.kube\nmicrok8s config > $HOME/.kube/config\nsudo reboot\n</code></pre> Check status of Kubernetes and addons <p> To retrieve the status of Kubernetes once, run the following command:</p> <pre><code>microk8s.status --wait-ready\n</code></pre> <p>To retrieve the status of Kubernetes periodically (e.g., every 1 second), run the following command:</p> <pre><code>watch -n 1 microk8s.status --wait-ready\n</code></pre> Check all resources in Kubernetes <p> To retrieve the status of the Kubernetes resources once, run the following command:</p> <pre><code>kubectl get all --all-namespaces\n</code></pre> <p>To retrieve the status of the Kubernetes resources periodically (e.g., every 1 second), run the following command:</p> <pre><code>watch -n 1 kubectl get all --all-namespaces\n</code></pre> Enable addons <p></p> <p>First, we need to enable the community plugins (maintained by third parties):</p> <pre><code>microk8s.enable community\n</code></pre> <p>The Addons to be enabled are:</p> <ul> <li><code>dns</code>: enables resolving the pods and services by name</li> <li><code>helm3</code>: required to install NATS</li> <li><code>hostpath-storage</code>: enables providing storage for the pods (required by <code>registry</code>)</li> <li><code>ingress</code>: deploys an ingress controller to expose the microservices outside Kubernetes</li> <li><code>registry</code>: deploys a private registry for the TFS controller images</li> <li><code>linkerd</code>: deploys the linkerd service mesh used for load balancing among replicas</li> <li><code>prometheus</code>: set of tools that enable TFS observability through per-component instrumentation</li> <li><code>metrics-server</code>: deploys the Kubernetes metrics server for API access to service metrics</li> </ul> <pre><code>microk8s.enable dns helm3 hostpath-storage ingress registry prometheus metrics-server linkerd\n</code></pre> <p>Important: Enabling some of the addons might take few minutes. Do not proceed with next steps until the addons are ready. Otherwise, the deployment might fail. To confirm everything is up and running:</p> <ol> <li>Periodically Check the status of Kubernetes until you see the addons [dns, ha-cluster, helm3, hostpath-storage, ingress, linkerd, metrics-server, prometheus, registry, storage] in the enabled block.</li> <li>Periodically Check Kubernetes resources until all pods are Ready and Running.</li> <li>If it takes too long for the Pods to be ready, we observed that rebooting the machine may help.</li> </ol> <p>Then, create aliases to make the commands easier to access:</p> <pre><code>sudo snap alias microk8s.helm3 helm3\nsudo snap alias microk8s.linkerd linkerd\n</code></pre> <p>To validate that <code>linkerd</code> is working correctly, run:</p> <pre><code>linkerd check\n</code></pre> <p>To validate that the <code>metrics-server</code> is working correctly, run:</p> <pre><code>kubectl top pods --all-namespaces\n</code></pre> <p>and you should see a screen similar to the <code>top</code> command in Linux, showing the columns namespace, pod name, CPU (cores), and MEMORY (bytes).</p> <p>In case pods are not starting, check information from pods logs. For example, linkerd is sensitive for proper /etc/resolv.conf syntax.</p> <pre><code>kubectl logs <podname> --namespace <namespace>\n</code></pre> <p>If the command shows an error message, also restarting the machine might help.</p> Stop, Restart, and Redeploy <p> Find below some additional commands you might need while you work with MicroK8s:</p> <pre><code>microk8s.stop # stop MicroK8s cluster (for instance, before power off your computer)\nmicrok8s.start # start MicroK8s cluster\nmicrok8s.reset # reset infrastructure to a clean state\n</code></pre> <p>If the following commands does not work to recover the MicroK8s cluster, you can redeploy it.</p> <p>If you want to keep MicroK8s configuration, use:</p> <pre><code>sudo snap remove microk8s\n</code></pre> <p>If you need to completely drop MicroK8s and its complete configuration, use:</p> <pre><code>sudo snap remove microk8s --purge\nsudo apt-get remove --purge docker.io docker-buildx\n</code></pre> <p>IMPORTANT: After uninstalling MicroK8s, it is convenient to reboot the computer (the VM if you work on a VM, or the physical computer if you use a physical computer). Otherwise, there are system configurations that are not correctly cleaned. Especially in what port forwarding and firewall rules matters.</p> <p>After the reboot, redeploy as it is described in this section.</p>"},{"location":"deployment_guide/deployment_guide/#13-deploy-teraflowsdn","title":"1.3. Deploy TeraFlowSDN","text":"<p>This section describes how to deploy TeraFlowSDN controller on top of MicroK8s using the environment configured in the previous sections.</p> Install prerequisites <p></p> <pre><code>sudo apt-get install -y git curl jq\n</code></pre> Clone the Git repository of the TeraFlowSDN controller <p> Clone from ETSI-hosted GitLab code repository:</p> <pre><code>mkdir ~/tfs-ctrl\ngit clone https://labs.etsi.org/rep/tfs/controller.git ~/tfs-ctrl\n</code></pre> <p>Important: The original H2020-TeraFlow project hosted on GitLab.com has been archieved and will not receive further contributions/updates. Please, clone from ETSI-hosted GitLab code repository.</p> Checkout the appropriate Git branch <p> TeraFlowSDN controller versions can be found in the appropriate release tags and/or branches as described in Home > Versions.</p> <p>By default the branch master is checked out and points to the latest stable version of the TeraFlowSDN controller, while branch develop contains the latest developments and contributions under test and validation.</p> <p>To switch to the appropriate branch run the following command, changing <code>develop</code> by the name of the branch you want to deploy:</p> <pre><code>cd ~/tfs-ctrl\ngit checkout develop\n</code></pre> Prepare a deployment script with the deployment settings <p> Create a new deployment script, e.g., <code>my_deploy.sh</code>, adding the appropriate settings as follows. This section provides just an overview of the available settings. An example <code>my_deploy.sh</code> script is provided in the root folder of the project for your convenience with full description of all the settings.</p> <p>Note: The example <code>my_deploy.sh</code> script provides reasonable settings for deploying a functional and complete enough TeraFlowSDN controller, and a brief description of their meaning. To see extended descriptions, check scripts in the <code>deploy</code> folder.</p> <pre><code>cd ~/tfs-ctrl\ntee my_deploy.sh >/dev/null << EOF\n# ----- TeraFlowSDN ------------------------------------------------------------\nexport TFS_REGISTRY_IMAGES=\"http://localhost:32000/tfs/\"\nexport TFS_COMPONENTS=\"context device ztp monitoring pathcomp service slice nbi webui load_generator\"\nexport TFS_IMAGE_TAG=\"dev\"\nexport TFS_K8S_NAMESPACE=\"tfs\"\nexport TFS_EXTRA_MANIFESTS=\"manifests/nginx_ingress_http.yaml\"\nexport TFS_GRAFANA_PASSWORD=\"admin123+\"\nexport TFS_SKIP_BUILD=\"\"\n\n# ----- CockroachDB ------------------------------------------------------------\nexport CRDB_NAMESPACE=\"crdb\"\nexport CRDB_EXT_PORT_SQL=\"26257\"\nexport CRDB_EXT_PORT_HTTP=\"8081\"\nexport CRDB_USERNAME=\"tfs\"\nexport CRDB_PASSWORD=\"tfs123\"\nexport CRDB_DATABASE=\"tfs\"\nexport CRDB_DEPLOY_MODE=\"single\"\nexport CRDB_DROP_DATABASE_IF_EXISTS=\"YES\"\nexport CRDB_REDEPLOY=\"\"\n\n# ----- NATS -------------------------------------------------------------------\nexport NATS_NAMESPACE=\"nats\"\nexport NATS_EXT_PORT_CLIENT=\"4222\"\nexport NATS_EXT_PORT_HTTP=\"8222\"\nexport NATS_REDEPLOY=\"\"\n\n# ----- QuestDB ----------------------------------------------------------------\nexport QDB_NAMESPACE=\"qdb\"\nexport QDB_EXT_PORT_SQL=\"8812\"\nexport QDB_EXT_PORT_ILP=\"9009\"\nexport QDB_EXT_PORT_HTTP=\"9000\"\nexport QDB_USERNAME=\"admin\"\nexport QDB_PASSWORD=\"quest\"\nexport QDB_TABLE_MONITORING_KPIS=\"tfs_monitoring_kpis\"\nexport QDB_TABLE_SLICE_GROUPS=\"tfs_slice_groups\"\nexport QDB_DROP_TABLES_IF_EXIST=\"YES\"\nexport QDB_REDEPLOY=\"\"\n\nEOF\n</code></pre> <p>The settings are organized in 4 sections: - Section <code>TeraFlowSDN</code>: - <code>TFS_REGISTRY_IMAGE</code> enables to specify the private Docker registry to be used, by default, we assume to use the Docker respository enabled in MicroK8s. - <code>TFS_COMPONENTS</code> specifies the components their Docker image will be rebuilt, uploaded to the private Docker registry, and deployed in Kubernetes. - <code>TFS_IMAGE_TAG</code> defines the tag to be used for Docker images being rebuilt and uploaded to the private Docker registry. - <code>TFS_K8S_NAMESPACE</code> specifies the name of the Kubernetes namespace to be used for deploying the TFS components. - <code>TFS_EXTRA_MANIFESTS</code> enables to provide additional manifests to be applied into the Kubernetes environment during the deployment. Typical use case is to deploy ingress controllers, service monitors for Prometheus, etc. - <code>TFS_GRAFANA_PASSWORD</code> lets you specify the password you want to use for the <code>admin</code> user of the Grafana instance being deployed and linked to the Monitoring component. - <code>TFS_SKIP_BUILD</code>, if set to <code>YES</code>, prevents rebuilding the Docker images. That means, the deploy script will redeploy existing Docker images without rebuilding/updating them.</p> <ul> <li>Section <code>CockroachDB</code>: enables to configure the deployment of the backend CockroachDB database.</li> <li> <p>Check example script <code>my_deploy.sh</code> for further details.</p> </li> <li> <p>Section <code>NATS</code>: enables to configure the deployment of the backend NATS message broker.</p> </li> <li> <p>Check example script <code>my_deploy.sh</code> for further details.</p> </li> <li> <p>Section <code>QuestDB</code>: enables to configure the deployment of the backend QuestDB timeseries database.</p> </li> <li>Check example script <code>my_deploy.sh</code> for further details.</li> </ul> Confirm that MicroK8s is running <p></p> <p>Run the following command:</p> <pre><code>microk8s status\n</code></pre> <p>If it is reported <code>microk8s is not running, try microk8s start</code>, run the following command to start MicroK8s:</p> <pre><code>microk8s start\n</code></pre> <p>Confirm everything is up and running:</p> <ol> <li>Periodically Check the status of Kubernetes until you see the addons [dns, ha-cluster, helm3, hostpath-storage, ingress, registry, storage] in the enabled block.</li> <li>Periodically Check Kubernetes resources until all pods are Ready and Running.</li> </ol> Deploy TFS controller <p> First, source the deployment settings defined in the previous section. This way, you do not need to specify the environment variables in each and every command you execute to operate the TFS controller. Be aware to re-source the file if you open new terminal sessions. Then, run the following command to deploy TeraFlowSDN controller on top of the MicroK8s Kubernetes platform.</p> <pre><code>cd ~/tfs-ctrl\nsource my_deploy.sh\n./deploy/all.sh\n</code></pre> <p>The script performs the following steps:</p> <ul> <li>Executes script <code>./deploy/crdb.sh</code> to automate deployment of CockroachDB database used by Context component.</li> <li>The script automatically checks if CockroachDB is already deployed.</li> <li>If there are settings instructing to drop the database and/or redeploy CockroachDB, it does the appropriate actions to honor them as defined in previous section.</li> <li>Executes script <code>./deploy/nats.sh</code> to automate deployment of NATS message broker used by Context component.</li> <li>The script automatically checks if NATS is already deployed.</li> <li>If there are settings instructing to redeploy the message broker, it does the appropriate actions to honor them as defined in previous section.</li> <li>Executes script <code>./deploy/qdb.sh</code> to automate deployment of QuestDB timeseries database used by Monitoring component.</li> <li>The script automatically checks if QuestDB is already deployed.</li> <li>If there are settings instructing to redeploy the timeseries database, it does the appropriate actions to honor them as defined in previous section.</li> <li>Executes script <code>./deploy/tfs.sh</code> to automate deployment of TeraFlowSDN.</li> <li>Creates the namespace defined in <code>TFS_K8S_NAMESPACE</code></li> <li>Creates secrets for CockroachDB, NATS, and QuestDB to be used by Context and Monitoring components.</li> <li>Builds the Docker images for the components defined in <code>TFS_COMPONENTS</code></li> <li>Tags the Docker images with the value of <code>TFS_IMAGE_TAG</code></li> <li>Pushes the Docker images to the repository defined in <code>TFS_REGISTRY_IMAGE</code></li> <li>Deploys the components defined in <code>TFS_COMPONENTS</code></li> <li>Creates the file <code>tfs_runtime_env_vars.sh</code> with the environment variables for the components defined in <code>TFS_COMPONENTS</code> defining their local host addresses and their port numbers.</li> <li>Applies extra manifests defined in <code>TFS_EXTRA_MANIFESTS</code> such as:<ul> <li>Creating an ingress controller listening at port 80 for HTTP connections to enable external access to the TeraFlowSDN WebUI, Grafana Dashboards, and Compute NBI interfaces.</li> <li>Deploying service monitors to enable monitoring the performance of the components, device drivers and service handlers.</li> </ul> </li> <li>Initialize and configure the Grafana dashboards (if Monitoring component is deployed)</li> <li>Report a summary of the deployment</li> <li>See Show Deployment and Logs</li> </ul>"},{"location":"deployment_guide/deployment_guide/#14-webui-and-grafana-dashboards","title":"1.4. WebUI and Grafana Dashboards","text":"<p>This section describes how to get access to the TeraFlowSDN controller WebUI and the monitoring Grafana dashboards.</p> Access the TeraFlowSDN WebUI <p> If you followed the installation steps based on MicroK8s, you got an ingress controller installed that exposes on TCP port 80.</p> <p>Besides, the ingress controller defines the following reverse proxy paths (on your local machine):</p> <ul> <li><code>http://127.0.0.1/webui</code>: points to the WebUI of TeraFlowSDN.</li> <li><code>http://127.0.0.1/grafana</code>: points to the Grafana dashboards. This endpoint brings access to the monitoring dashboards of TeraFlowSDN. The credentials for the <code>admin</code>user are those defined in the <code>my_deploy.sh</code> script, in the <code>TFS_GRAFANA_PASSWORD</code> variable.</li> <li><code>http://127.0.0.1/restconf</code>: points to the Compute component NBI based on RestCONF. This endpoint enables connecting external software, such as ETSI OpenSourceMANO NFV Orchestrator, to TeraFlowSDN.</li> </ul> <p>Note: In the creation of the VM, a forward from host TCP port 8080 to VM's TCP port 80 is configured, so the WebUIs and REST APIs of TeraFlowSDN should be exposed on the endpoint <code>127.0.0.1:8080</code> of your local machine instead of <code>127.0.0.1:80</code>.</p>"},{"location":"deployment_guide/deployment_guide/#15-show-deployment-and-logs","title":"1.5. Show Deployment and Logs","text":"<p>This section presents some helper scripts to inspect the status of the deployment and the logs of the components. These scripts are particularly helpful for troubleshooting during execution of experiments, development, and debugging.</p> Report the deployment of the TFS controller <p></p> <p>The summary report given at the end of the Deploy TFS controller procedure can be generated manually at any time by running the following command. You can avoid sourcing <code>my_deploy.sh</code> if it has been already done.</p> <pre><code>cd ~/tfs-ctrl\nsource my_deploy.sh\n./deploy/show.sh\n</code></pre> <p>Use this script to validate that all the pods, deployments, replica sets, ingress controller, etc. are ready and have the appropriate state, e.g., running for Pods, and the services are deployed and have appropriate IP addresses and port numbers.</p> Report the log of a specific TFS controller component <p></p> <p>A number of scripts are pre-created in the <code>scripts</code> folder to facilitate the inspection of the component logs. For instance, to dump the log of the Context component, run the following command. You can avoid sourcing <code>my_deploy.sh</code> if it has been already done.</p> <pre><code>source my_deploy.sh\n./scripts/show_logs_context.sh\n</code></pre>"},{"location":"development_guide/development_guide/","title":"2. Development Guide","text":""},{"location":"development_guide/development_guide/#21-configure-environment","title":"2.1. Configure Environment","text":""},{"location":"development_guide/development_guide/#211-python","title":"2.1.1. PythonUpgrade the Ubuntu distribution <p>Skip this step if you already did it during the installation of your machine.</p> <pre><code>sudo apt-get update -y\nsudo apt-get dist-upgrade -y\n</code></pre> Install PyEnv dependencies <p></p> <pre><code>sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget \\\n curl llvm git libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev\n</code></pre> Install PyEnv <p></p> <p>We recommend installing PyEnv through PyEnv Installer. Below you can find the instructions, but we refer you to the link for updated instructions.</p> <pre><code>curl https://pyenv.run | bash\n# When finished, edit ~/.bash_profile // ~/.profile // ~/.bashrc as the installer proposes.\n# In general, it means to append the following lines to ~/.bashrc:\nexport PYENV_ROOT=\"$HOME/.pyenv\"\ncommand -v pyenv >/dev/null || export PATH=\"$PYENV_ROOT/bin:$PATH\"\neval \"$(pyenv init -)\"\neval \"$(pyenv virtualenv-init -)\"\n</code></pre> <p>In case .bashrc is not linked properly to your profile, you may need to append the following line into your local .profile file:</p> <pre><code># Open ~/.profile and append this line:\n+source \"$HOME\"/.bashrc\n</code></pre> Restart the machine <p> Restart the machine for all the changes to take effect.</p> <pre><code>sudo reboot\n</code></pre> Install Python 3.9 over PyEnv <p></p> <p>ETSI TeraFlowSDN uses Python 3.9 by default. You should install the latest stable update of Python 3.9, i.e., avoid \"-dev\" versions. To find the latest version available in PyEnv, you can run the following command:</p> <pre><code>pyenv install --list | grep \" 3.9\"\n</code></pre> <p>At the time of writing, this command will output the following list:</p> <pre><code> 3.9.0\n 3.9-dev\n 3.9.1\n 3.9.2\n 3.9.4\n 3.9.5\n 3.9.6\n 3.9.7\n 3.9.8\n 3.9.9\n 3.9.10\n 3.9.11\n 3.9.12\n 3.9.13\n 3.9.14 \n 3.9.15\n 3.9.16 ** always select the latest version **\n</code></pre> <p>Therefore, the latest stable version is Python 3.9.16. To install this version, you should run:</p> <pre><code>pyenv install 3.9.16\n # This command might take some minutes depending on your Internet connection speed \n # and the performance of your machine.\n</code></pre> Create the Virtual Environment for TeraFlowSDN <p> The following commands create a virtual environment named as <code>tfs</code> using Python 3.9 and associate that environment with the current folder, i.e., <code>~/tfs-ctrl</code>. That way, when you are in that folder, the associated virtual environment will be used, thus inheriting the Python interpreter, i.e., Python 3.9, and the Python packages installed on it.</p> <pre><code>cd ~/tfs-ctrl\npyenv virtualenv 3.9.16 tfs\npyenv local 3.9.16/envs/tfs\n</code></pre> <p>After completing these commands, you should see in your prompt that now you're within the virtual environment <code>3.9.16/envs/tfs</code> on folder <code>~/tfs-ctrl</code>:</p> <pre><code>(3.9.16/envs/tfs) tfs@tfs-vm:~/tfs-ctrl$\n</code></pre> <p>In case that the correct pyenv does not get automatically activated when you change to the tfs-ctrl/ folder, then execute the following command:</p> <pre><code>cd ~/tfs-ctrl\npyenv activate 3.9.16/envs/tfs\n</code></pre> Install the basic Python packages within the virtual environment <p> From within the <code>3.9.16/envs/tfs</code> environment on folder <code>~/tfs-ctrl</code>, run the following commands to install the basic Python packages required to work with TeraFlowSDN.</p> <pre><code>cd ~/tfs-ctrl\n./install_requirements.sh\n</code></pre> <p>Some dependencies require to re-load the session, so log-out and log-in again.</p> Generate the Python code from the gRPC Proto messages and services <p></p> <p>The components, e.g., microservices, of the TeraFlowSDN controller, in general, use a gRPC-based open API to interoperate. All the protocol definitions can be found in sub-folder <code>proto</code> within the root project folder. For additional details on gRPC, visit the official web-page gRPC.</p> <p>In order to interact with the components, (re-)generate the Python code from gRPC definitions running the following command:</p> <pre><code>cd ~/tfs-ctrl\nproto/generate_code_python.sh\n</code></pre>","text":"<p>This section describes how to configure the Python environment to run experiments and develop code for the ETSI TeraFlowSDN controller. In particular, we use PyEnv to install the appropriate version of Python and manage the virtual environments.</p>"},{"location":"development_guide/development_guide/#212-java-quarkus","title":"2.1.2. Java (Quarkus) <p>This section describe the steps needed to create a development environment for TFS components implemented in Java. Currently, ZTP and Policy components have been developed in Java (version 11) and use the Quarkus framework, which enables kubernetes-native development.</p> Install JDK <p> To begin, make sure that you have java installed and in the correct version</p> <pre><code>java --version\n</code></pre> <p>If you don't have java installed you will get an error like the following:</p> <pre><code>Command 'java' not found, but can be installed with:\n\nsudo apt install default-jre # version 2:1.11-72build1, or\nsudo apt install openjdk-11-jre-headless # version 11.0.14+9-0ubuntu2\nsudo apt install openjdk-17-jre-headless # version 17.0.2+8-1\nsudo apt install openjdk-18-jre-headless # version 18~36ea-1\nsudo apt install openjdk-8-jre-headless # version 8u312-b07-0ubuntu1\n</code></pre> <p>In which case you should use the following command to install the correct version:</p> <pre><code>sudo apt install openjdk-11-jre-headless\n</code></pre> <p>Else you should get something like the following:</p> <pre><code>openjdk 11.0.18 2023-01-17\nOpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1)\nOpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1, mixed mode, sharing)\n</code></pre> Compiling and testing existing components <p> In the root directory of the existing Java components you will find an executable maven wrapper named <code>mvnw</code>. You could use this executable, which is already configured in pair with the components, instead of your local maven installation. So for example if you want to compile the project you would run the following:</p> <pre><code>./mvnw compile\n</code></pre> VS Code Quarkus plugin <p> In case you are using VS Code for development, we suggest to install the official Quarkus extension. The extension should be able to automatically find the current open project and integrate with the above <code>mvnw</code> maven wrapper, making it easier to control the maven lifecycle. Make sure that you open the specific component directory (i.e., <code>src/ztp</code> or <code>src/policy</code>) and not the general controller one (i.e., <code>src</code>.</p> New Java TFS component <p></p> <p>Sample Project</p> <p>If you want to create a new TFS component written in Java you could generate a new Quarkus project based on the following project:</p> <p>TFS Sample Quarkus Project</p> <p>In that way, you should have most of the libraries you would need to integrate with the rest of the TFS Components. Feel free however to add or remove libraries depending on your needs.</p> <p>Initial setup</p> <p>If you used the sample project above, you should have a project with a basic structure. However there are some steps that you should take before starting development.</p> <p>First make sure that you copy the protobuff files, that are found in the root directory of the TFS SDN controller, to the <code>new-component/src/main/proto</code> directory.</p> <p>Next you should create the following files:</p> <ul> <li><code>new-component/.gitlab-ci.yml</code></li> <li><code>new-component/Dockerfile</code></li> <li><code>new-component/src/resources/application.yaml</code></li> </ul> <p>We suggest to copy the respective files from existing components (Automation and Policy) and change them according to your needs.</p>","text":""},{"location":"development_guide/development_guide/#213-java-maven","title":"2.1.3. Java (Maven) <p>Page under construction</p>","text":""},{"location":"development_guide/development_guide/#214-rust","title":"2.1.4. Rust <p>Page under construction</p>","text":""},{"location":"development_guide/development_guide/#215-erlang","title":"2.1.5. Erlang <p>This section describes how to configure the Erlang environment to run experiments and develop code for the ETSI TeraFlowSDN controller.</p> <p>First we need to install Erlang. There is multiple way, for development we will be using ASDF, a tool that allows the installation of multiple version of Erlang at the same time, and switch from one version to the other at will.</p> <ul> <li>First, install any missing dependencies:</li> </ul> <pre><code>sudo apt install curl git autoconf libncurses-dev build-essential m4 libssl-dev \n</code></pre> <ul> <li>Download ASDF tool to the local account:</li> </ul> <pre><code>git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.10.2\n</code></pre> <ul> <li>Make ASDF activate on login by adding these lines at the end of the <code>~/.bashrc</code> file:</li> </ul> <pre><code>. $HOME/.asdf/asdf.sh\n. $HOME/.asdf/completions/asdf.bash\n</code></pre> <ul> <li>Logout and log back in to activate ASDF.</li> </ul> <p>ASDF supports multiple tools by installing there corresponding plugins.</p> <ul> <li>Install ASDF plugin for Erlang:</li> </ul> <pre><code>asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git\n</code></pre> <ul> <li>Install a version of Erlang:</li> </ul> <pre><code>asdf install erlang 24.3.4.2\n</code></pre> <ul> <li>Activate Erlang locally for TFS controller. This will create a local file called <code>.tool-versions</code> defining which version of the tools to use when running under the current directory:</li> </ul> <pre><code>cd tfs-ctrl/\nasdf local erlang 24.3.4.2\n</code></pre> <p>Erlang projects uses a build tool called rebar3. It is used to manager project dependenecies, compile a project and generate project releases.</p> <ul> <li>Install rebar3 localy from source:</li> </ul> <pre><code>cd ~\ngit clone https://github.com/erlang/rebar3.git\ncd rebar3\nasdf local erlang 24.3.4.2\n./bootstrap\n./rebar3 local install\n</code></pre> <ul> <li>Update <code>~/.bashrc</code> to use rebar3 by adding this line at the end:</li> </ul> <pre><code>export PATH=$HOME/.cache/rebar3/bin:$PATH\n</code></pre> <ul> <li>Logout and log back in.</li> </ul>","text":""},{"location":"development_guide/development_guide/#216-kotlin","title":"2.1.6. Kotlin <p>This section describes the steps needed to establish a development environment for TFS (TeraFlowSDN) components implemented in Kotlin. Currently, the <code>Gateway</code> component stands as the sole component developed in Kotlin.</p> Install Kotlin <p> To begin, make sure that you have kotlin installed and its current version:</p> <pre><code>kotlin -version\n</code></pre> <p>If you don't have kotlin installed you will get an error like the following:</p> <pre><code>Command 'kotlin' not found, but can be installed with:\nsudo snap install --classic kotlin\n</code></pre> <p>In which case you should use the following command to install the correct version:</p> <pre><code> sudo snap install --classic kotlin\n</code></pre> <p>Currently, the recommended version is 1.6.21, which uses Java Runtime Environment (JRE) version 11.</p> Compiling and testing existing components <p> To compile a Kotlin project using Gradle, similarly to using the Maven wrapper (mvnw) for Java projects, you can use the Gradle wrapper (gradlew) within the root directory of your Kotlin component, specifically the gateway directory.</p> <p>Navigate to the gateway directory within your Kotlin project. Ensure that it contains the gradlew script along with the gradle directory. Then, create a directory named <code>proto</code> and move all the files with extension <code>.proto</code> in this way:</p> <pre><code>mkdir proto\ncp ../../../proto/*.proto ./proto \n</code></pre> <p>For building the application, open a terminal or command prompt, navigate to the gateway directory, and run the following command:</p> <pre><code>./gradlew build\n</code></pre> <p>The following program runs the gateway application:</p> <pre><code>./gradlew runServer \n</code></pre> New Kotlin TFS component <p></p> <p>Sample Project</p> <p>If you want to create a new TFS component written in Kotlin you could generate a Kotlin project using <code>gradle</code>. The recommended version is 7.1. Follow the following Gradle guide for its installation. For building the prokect follow this link instead.</p> <p>From inside the new project directory, run the init task using the following command in a terminal: <code>gradle init</code>. </p> <p>The output will look like this:</p> <pre><code>$ gradle init\n\nSelect type of project to generate:\n 1: basic\n 2: application\n 3: library\n 4: Gradle plugin\nEnter selection (default: basic) [1..4] 2\n\nSelect implementation language:\n 1: C++\n 2: Groovy\n 3: Java\n 4: Kotlin\n 5: Scala\n 6: Swift\nEnter selection (default: Java) [1..6] 4\n\nSelect build script DSL:\n 1: Groovy\n 2: Kotlin\nEnter selection (default: Groovy) [1..2] 1\n\nProject name (default: demo):\nSource package (default: demo):\n\n\nBUILD SUCCESSFUL\n2 actionable tasks: 2 executed\n</code></pre> <p>Initial setup</p> <p>The <code>gradle init</code> command generates the new project. </p> <p>First, ensure the protobuf files are copied from the root directory of the TFS SDN controller. Run the following command in the directory of the new project:</p> <pre><code>mkdir proto \ncp TFS/project/root/path/proto/*.proto ./proto/\n</code></pre> <p>The file <code>build.gradle.ktl</code> is fundamental as it manages dependencies. Adjust it for adding external libraries. </p> <p>Next you should create the following files:</p> <ol> <li><code>new-component/.gitlab-ci.yml</code></li> <li><code>new-component/Dockerfile</code></li> </ol> <p>We recommend leveraging the structures and configurations found in the files of existing components for inspiration.</p> <p>Docker Container This project operates with Docker containers. Ensure the production of the container version for your component. To generate the container version of the project, modify the 'new-component/Dockerfile.' Execute the following command from the project's root directory:</p> <pre><code>docker build -t new-image -f new-component/Dockerfile ./\n</code></pre>","text":""},{"location":"development_guide/development_guide/#22-configure-vscode","title":"2.2. Configure VScode","text":"Install VSCode and the required extensions <p>If not already done, install VSCode and the \"Remote SSH\" extension on your local machine, not in the VM.</p> <p>Note: \"Python\" extension is not required here. It will be installed later on the VSCode server running on the VM.</p> Configure the \"Remote SSH\" extension <p></p> <ul> <li>Go to left icon \"Remote Explorer\"</li> <li>Click the \"gear\" icon next to \"SSH TARGETS\" on top of \"Remote Explorer\" bar</li> <li>Choose to edit \"<...>/.ssh/config\" file (or equivalent)</li> <li>Add the following entry (assuming previous port forwarding configuration):</li> </ul> <pre><code>Host TFS-VM\n HostName 127.0.0.1\n Port 2200\n ForwardX11 no\n User tfs\n</code></pre> <ul> <li>Save the file</li> <li>An entry \"TFS-VM\" should appear on \"SSH TARGETS\".</li> </ul> Connect VSCode to the VM through \"Remote SSH\" extension <p></p> <ul> <li>Right-click on \"TFS-VM\"</li> <li>Select \"Connect to Host in Current Window\"</li> <li>Reply to the questions asked</li> <li>Platform of the remote host \"TFS-VM\": Linux</li> <li>\"TFS-VM\" has fingerprint \"\". Do you want to continue?: Continue <li>Type tfs user's password: tfs123</li> <li>You should be now connected to the TFS-VM.</li> <p>Note: if you get a connection error message, the reason might be due to wrong SSH server fingerprint. Edit file \"<...>/.ssh/known_hosts\" on your local user account, check if there is a line starting with \"[127.0.0.1]:2200\" (assuming previous port forwarding configuration), remove the entire line, save the file, and retry connection.</p> Add SSH key to prevent typing the password every time <p> This step creates an SSH key in the VM and installs it on the VSCode to prevent having to type the password every time.</p> <ul> <li>In VSCode (connected to the VM), click menu \"Terminal > New Terminal\"</li> <li>Run the following commands on the VM's terminal through VSCode</li> </ul> <pre><code>ssh-keygen -t rsa -b 4096 -f ~/.ssh/tfs-vm.key\n # leave password empty\nssh-copy-id -i ~/.ssh/tfs-vm.key.pub tfs@10.0.2.10\n # tfs@10.0.2.10's password: <type tfs user's password: tfs123>\nrm .ssh/known_hosts \n</code></pre> <ul> <li>In VSCode, click left \"Explorer\" panel to expand, if not expanded, and click \"Open Folder\" button.</li> <li>Choose \"/home/tfs/\"</li> <li>Type tfs user's password when asked</li> <li>Trust authors of the \"/home/tfs [SSH: TFS-VM]\" folder when asked</li> <li>Right click on the file \"tfs-vm.key\" in the file explorer</li> <li>Select \"Download...\" option</li> <li>Download the file into your user's accout \".ssh\" folder</li> <li> <p>Delete files \"tfs-vm.key\" and \"tfs-vm.key.pub\" on the TFS-VM.</p> </li> <li> <p>In VSCode, click left \"Remote Explorer\" panel to expand</p> </li> <li>Click the \"gear\" icon next to \"SSH TARGETS\" on top of \"Remote Explorer\" bar</li> <li>Choose to edit \"<...>/.ssh/config\" file (or equivalent)</li> <li>Find entry \"Host TFS-VM\" and update it as follows:</li> </ul> <pre><code>Host TFS-VM\n HostName 127.0.0.1\n Port 2200\n ForwardX11 no\n User tfs\n IdentityFile \"<path to the downloaded identity private key file>\"\n</code></pre> <ul> <li>Save the file</li> <li>From now, VSCode will use the identity file to connect to the TFS-VM instead of the user's password.</li> </ul> Install VSCode Python Extension (in VSCode server) <p> This step installs Python extensions in VSCode server running in the VM.</p> <ul> <li>In VSCode (connected to the VM), click left button \"Extensions\"</li> <li>Search \"Python\" extension in the extension Marketplace.</li> <li> <p>Install official \"Python\" extension released by Microsoft.</p> <ul> <li>By default, since you're connected to the VM, it will be installed in the VSCode server running in the VM.</li> </ul> </li> <li> <p>In VSCode (connected to the VM), click left button \"Explorer\"</p> </li> <li>Click \"Ctrl+Alt+P\" and type \"Python: Select Interpreter\". Select option \"Python: 3.9.13 64-bit ('tfs')\"</li> </ul> Define environment variables for VSCode <p> The source code in the TFS controller project is hosted in folder <code>src/</code>. To help VSCode find the Python modules and packages, add the following file into your working space root folder:</p> <pre><code>echo \"PYTHONPATH=./src\" >> ~/tfs-ctrl/.env\n</code></pre>"},{"location":"development_guide/development_guide/#23-develop-a-component-wip","title":"2.3. Develop A Component (WIP)","text":"<p>Page under construction</p>"},{"location":"testing/postman/","title":"Postman","text":"<p>In this section we can use Postman to publish an API as a provider and use it as an invoker.</p>"},{"location":"testing/postman/#requisites","title":"Requisites","text":"<ul> <li>We will need to have Node.js installed since we will use a small script to create the CSRs of the certificates.</li> <li>An instance of CAPIF (If it is not local, certain variables would have to be modified both in the Node.js script and in the Postman environment variables).</li> </ul>"},{"location":"testing/postman/#first-steps","title":"First steps","text":"<ol> <li>Install the Node dependencies package.json to run the script with:</li> </ol> <pre><code>npm i\n</code></pre> <ol> <li>Run the script.js with the following command:</li> </ol> <pre><code>node script.js\n</code></pre> <ol> <li>Import Postman collection and environment variables (CAPIF.postman_collection.json and CAPIF.postman_environment.json)</li> <li>Select CAPIF Environment before start testing.</li> </ol>"},{"location":"testing/postman/#remote-capif","title":"Remote CAPIF","text":"<p>If the CAPIF is not local, the host and port of both the CAPIF and the register would have to be specified in the variables, and the CAPIF_HOSTNAME in the script, necessary to obtain the server certificate.</p> <p>Enviroments in Postman</p> <pre><code>CAPIF_HOSTNAME capifcore\nCAPIF_PORT 8080\nREGISTER_HOSTNAME register\nREGISTER_PORT 8084\n</code></pre> <p>Const in script.js</p> <pre><code>CAPIF_HOSTNAME capifcore\n</code></pre>"},{"location":"testing/postman/#capif-flows","title":"CAPIF Flows","text":"<p>Once the first steps have been taken, we can now use Postman requests. These requests are numbered in the order that must be followed to obtain everything necessary from CAPIF.</p>"},{"location":"testing/postman/#creation-of-user-by-admin","title":"Creation of User by Admin","text":"<p>The first step would be for an administrator to create a user with which a provider and an invoker will be created. To do this, the admin must log in to obtain the token needed in admin requests.</p>"},{"location":"testing/postman/#01-login_admin","title":"01-Login_admin","text":""},{"location":"testing/postman/#02-creation-of-user","title":"02-Creation of User","text":""},{"location":"testing/postman/#publication-of-an-api","title":"Publication of an API","text":"<p>The next step is to register a provider using the user created by the administrator in order to publish an API.</p>"},{"location":"testing/postman/#03-getauth_provider","title":"03-getauth_provider","text":""},{"location":"testing/postman/#04-onboard_provider","title":"04-onboard_provider","text":"<p>At this point we move on to using certificate authentication in CAPIF. In Postman it is necessary to add the certificates manually and using more than one certificate for the same host as we do in CAPIF complicates things. For this reason, we use the script to overwrite a certificate and a key when it is necessary to have a specific one.</p> <p>To configure go to settings in Postman and open the certificates section. </p> <ul> <li>Here, activate the CA certificates option and add the ca_cert.pem file found in the Responses folder.</li> <li>Adds a client certificate specifying the CAPIF host being used and the files client_cert.crt and client_key.key in the Responses folder.</li> </ul> <p>Once this is done, the node script will be in charge of changing the certificate that is necessary in each request.</p>"},{"location":"testing/postman/#05-publish_api","title":"05-publish_api","text":"<p>Once the api is published, we can start it. In this case we have a test one created in python called hello_api.py that can be executed with the following command:</p> <pre><code>python3 hello_api.py\n</code></pre> <p>The API publication interface is set to localhost with port 8088, so the service must be set up locally. If you wanted to build it on another site, you would have to change the interface description in the body of publish_api.</p> <p>With this the provider part would be finished.</p>"},{"location":"testing/postman/#calling-the-api","title":"Calling the API","text":"<p>Finally, we will create an invoker with the user given by the administrator to be able to use the published api.</p>"},{"location":"testing/postman/#06-getauth_invoker","title":"06-getauth_invoker","text":""},{"location":"testing/postman/#07-onboard_invoker","title":"07-onboard_invoker","text":"<p>At this point we move on to using certificate authentication in CAPIF. If you did not configure the provider's certificates, you would have to do it now.</p>"},{"location":"testing/postman/#08-discover","title":"08-discover","text":""},{"location":"testing/postman/#09-security_context","title":"09-security_context","text":""},{"location":"testing/postman/#10-get_token","title":"10-get_token","text":""},{"location":"testing/postman/#11-call_service","title":"11-call_service","text":"<p>With this, we would have made the API call and finished the flow.</p>"},{"location":"testing/postman/#other-requests","title":"Other requests","text":"<p>Other requests that we have added are the following:</p> <ul> <li>offboard_provider Performs offboarding of the provider, thereby eliminating the published APIs.</li> <li>offboard_invoker Offboards the invoker, also eliminating access to the APIs of that invoker.</li> <li>remove_user Delete the user.</li> <li>refresh_admin_token Return a new access token to the admin.</li> </ul>"},{"location":"testing/postman/#notes","title":"Notes","text":"<ul> <li>This process is designed to teach how requests are made in Postman and the flow that should be followed to publish and use an API.</li> <li>It is possible that if external CAPIFs are used (Public CAPIF) the test data may already be used or the API already registered.</li> <li>It is necessary to have the Node service running to make the certificate change for the requests, otherwise it will not work.</li> <li>We are working on adding more requests to the Postman collection.</li> <li>This collection is a testing guide and is recommended for testing purposes only.</li> </ul>"},{"location":"testing/robotframework/","title":"Robot Framework","text":""},{"location":"testing/robotframework/#steps-to-test","title":"Steps to Test","text":"<p>To run any test locally you will need docker and docker-compose installed in order run services and execute test plan. Steps will be:</p> <ul> <li> <p>Run All Services: See section Run All CAPIF Services</p> </li> <li> <p>Run desired tests: At this point we have 2 options:</p> </li> <li> <p>Using helper script: Script Test Execution</p> </li> <li>Build robot docker image and execute manually robot docker: Manual Build And Test Execution</li> </ul>"},{"location":"testing/robotframework/#script-test-execution","title":"Script Test Execution","text":"<p>This script will build robot docker image if it's need and execute tests selected by \"include\" option. Just go to service folder, execute and follow steps.</p> <pre><code>./run_capif_tests.sh --include <TAG>\n</code></pre> <p>Results will be stored at /results <p>Please check parameters (include) under Test Execution at Manual Build And Test Execution.</p>"},{"location":"testing/robotframework/#mock-server","title":"Mock Server","text":"<p>Some tests on Test Plans require mockserver. That mock server must be deployed and reachable by Robot Framework and CCF under test.</p> <p>To run Mock Server locally you can just execute the next script:</p> <pre><code>cd services\n./run_mock_server.sh\n\nor\n./run.sh -s\n</code></pre> <p>If you want to launch only tests that not needed mockserver, just add \"--exclude mockserver\" parameter to robot execution:</p> <pre><code>./run_capif_tests.sh --include <TAG> --exclude mockserver\n</code></pre> <p>After run tests the Mock Server can be removed from local deployment:</p> <pre><code>./clean_mock_server.sh\n\nor\n./clean_capif_docker_services.sh -s\n</code></pre>"},{"location":"testing/robotframework/#manual-build-and-test-execution","title":"Manual Build And Test Execution","text":"<ul> <li>Build Robot docker image:</li> </ul> <pre><code>cd tools/robot\ndocker build . -t capif-robot-test:latest\n</code></pre> <ul> <li>Tests Execution:</li> </ul> <p>Execute all tests locally:</p> <pre><code><PATH_TO_REPOSITORY>=path in local machine to repository cloned.\n<PATH_RESULT_FOLDER>=path to a folder on local machine to store results of Robot Framework execution.\n<CAPIF_HOSTNAME>=Is the hostname set when run.sh is executed, by default it is capifcore.\n<CAPIF_HTTP_PORT>=This is the port to reach when robot framework want to reach CAPIF deployment using http, this should be set to port without TLS set on Nginx, 8080 by default.\n<CAPIF_HTTPS_PORT>=This is the port to be used when we want to use https connection, this should be set to port with TLS set on Nginx, 443 by default\n<CAPIF_REGISTER>=This is the hostname of register service deployed. By default it is register.\n<CAPIF_REGISTER_PORT>=This is the port to be used to reach register service deployed. By default it is 8084.\n<CAPIF_VAULT>=This is the hostname of vault service. By default it is vault.\n<CAPIF_VAULT_PORT>=This is the port to be used to reach vault service. By default it is 8200.\n<CAPIF_VAULT_TOKEN>=Vault token to be used on request through vault. By default it is \"read-ca-token\".\n<MOCK_SERVER_URL>=Setup Mock server url to be used in notifications at tests marked with mockserver tag. By default it is not set.\n\nTo execute all tests run :\ndocker run -ti --rm --network=\"host\" \\\n --add-host host.docker.internal:host-gateway \\\n --add-host vault:host-gateway \\\n --add-host register:host-gateway \\\n --add-host mock-server:host-gateway \\\n -v <PATH_TO_REPOSITORY>/tests:/opt/robot-tests/tests \\\n -v <PATH_RESULT_FOLDER>:/opt/robot-tests/results capif-robot-test:latest \\\n --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \\\n --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \\\n --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \\\n --variable CAPIF_REGISTER:$CAPIF_REGISTER \\\n --variable CAPIF_REGISTER_PORT:$CAPIF_REGISTER_PORT \\\n --variable CAPIF_VAULT:$CAPIF_VAULT \\\n --variable CAPIF_VAULT_PORT:$CAPIF_VAULT_PORT \\\n --variable CAPIF_VAULT_TOKEN:$CAPIF_VAULT_TOKEN \\\n --variable MOCK_SERVER_URL:$MOCK_SERVER_URL \\\n --include all\n</code></pre> <p>Execute specific tests locally:</p> <pre><code>To run more specific tests, for example, only one functionality:\n<TAG>=Select one from list:\n \"capif_api_acl\",\n \"capif_api_auditing_service\",\n \"capif_api_discover_service\",\n \"capif_api_events\",\n \"capif_api_invoker_management\",\n \"capif_api_logging_service\",\n \"capif_api_provider_management\",\n \"capif_api_publish_service\",\n \"capif_security_api\n\nAnd Run:\ndocker run -ti --rm --network=\"host\" \\\n --add-host host.docker.internal:host-gateway \\\n --add-host vault:host-gateway \\\n --add-host register:host-gateway \\\n --add-host mock-server:host-gateway \\\n -v <PATH_TO_REPOSITORY>/tests:/opt/robot-tests/tests \\\n -v <PATH_RESULT_FOLDER>:/opt/robot-tests/results capif-robot-test:latest \\\n --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \\\n --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \\\n --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \\\n --variable CAPIF_REGISTER:$CAPIF_REGISTER \\\n --variable CAPIF_REGISTER_PORT:$CAPIF_REGISTER_PORT \\\n --variable CAPIF_VAULT:$CAPIF_VAULT \\\n --variable CAPIF_VAULT_PORT:$CAPIF_VAULT_PORT \\\n --variable CAPIF_VAULT_TOKEN:$CAPIF_VAULT_TOKEN \\\n --variable MOCK_SERVER_URL:$MOCK_SERVER_URL \\\n --include <TAG>\n</code></pre>"},{"location":"testing/robotframework/#test-result-review","title":"Test result review","text":"<p>In order to Review results after tests, you can check general report at /report.html or if you need more detailed information /log.html, example: <ul> <li> <p>Report: </p> </li> <li> <p>Detailed information: </p> </li> </ul> <p>NOTE: If you need more detail at Robot Framework Logs you can set log level option just adding to command --loglevel DEBUG</p>"},{"location":"testing/testplan/","title":"Test Plan Index","text":"<p>List of Common API Services implemented:</p> <ul> <li>Common Operations</li> <li>Api Invoker Management</li> <li>Api Provider Management</li> <li>Api Publish Service</li> <li>Api Discover Service</li> <li>Api Events Service</li> <li>Api Security Service</li> <li>Api Logging Service</li> <li>Api Auditing Service</li> <li>Api Access Control Policy</li> </ul>"},{"location":"testing/testplan/api_access_control_policy/","title":"Test Plan for CAPIF Api Access Control Policy","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_access_control_policy/#test-case-1-retrieve-acl","title":"Test Case 1: Retrieve ACL","text":"<p>Test ID: capif_api_acl-1</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL from CAPIF</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain only one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-2-retrieve-acl-with-2-service-apis-published","title":"Test Case 2: Retrieve ACL with 2 Service APIs published","text":"<p>Test ID: capif_api_acl-2</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL from CAPIF for 2 different serviceApis published.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had two Service API Published on CAPIF</li> <li>API Invoker had a Security Context for both Service APIs published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information for service_1.</li> <li>Provider Get ACL information for service_2.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId1</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId2</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId2}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-3-retrieve-acl-with-security-context-created-by-two-different-invokers","title":"Test Case 3: Retrieve ACL with security context created by two different Invokers","text":"<p>Test ID: capif_api_acl-3</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL from CAPIF containing 2 objects.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>Two API Invokers had a Security Context for same Service API published by provider.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Repeat previous 3 steps in order to have a new Invoker.</p> </li> <li> <p>Provider Retrieve ACL for serviceApiId</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain two objects.</li> <li>One object must match with apiInvokerId1 and the other one with apiInvokerId2 an registered previously.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-4-retrieve-acl-filtered-by-api-invoker-id","title":"Test Case 4: Retrieve ACL filtered by api-invoker-id","text":"<p>Test ID: capif_api_acl-4</p> <p>Description:</p> <p>This test case will check that an API Provider can retrieve ACL filtering by apiInvokerId from CAPIF containing 1 objects.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>Two API Invokers had a Security Context for same Service API published by provider.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information with query parameter indicating first api-invoker-id.</li> <li>Provider Get ACL information with query parameter indicating second api-invoker-id.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Repeat previous 3 steps in order to have a new Invoker.</p> </li> <li> <p>Provider Retrieve ACL for serviceApiId1</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={apiInvokerId1}</li> <li>Use serviceApiId, aefId and apiInvokerId1</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId2</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={apiInvokerId2}</li> <li>Use serviceApiId, aefId and apiInvokerId2</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with apiInvokerId1.</li> </ol> </li> </ol> </li> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with apiInvokerId2.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-5-retrieve-acl-filtered-by-supported-features","title":"Test Case 5: Retrieve ACL filtered by supported-features","text":"<p>Test ID: capif_api_acl-5</p> <p>Description:</p> <p>CURRENTLY NOT SUPPORTED FEATURE</p> <p>This test case will check that an API Provider can retrieve ACL filtering by supportedFeatures from CAPIF containing 1 objects.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>Two API Invokers had a Security Context for same Service API published by provider.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1 and service_2</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information with query parameter indicating first supported-features.</li> <li>Provider Get ACL information with query parameter indicating second supported-features.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker for both published APIs</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Repeat previous 3 steps in order to have a new Invoker.</p> </li> <li> <p>Provider Retrieve ACL for serviceApiId</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}&supported-features={apiInvokerId1}</li> <li>Use serviceApiId, aefId and apiInvokerId1</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL for serviceApiId</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId1}?aef-id=${aef_id}&supported-features={apiInvokerId2}</li> <li>Use serviceApiId, aefId and apiInvokerId2</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with supportedFeatures1.</li> </ol> </li> </ol> </li> <li> <p>ACL Response:</p> <ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>Contain one objects.</li> <li>Object must match with supportedFeatures1.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-6-retrieve-acl-with-aef-id-not-valid","title":"Test Case 6: Retrieve ACL with aef-id not valid","text":"<p>Test ID: capif_api_acl-6</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF if aef-id is not valid</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${AEF_ID_NOT_VALID}</li> <li>Use serviceApiId and AEF_ID_NOT_VALID</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {service_api_id}, aef_id: {aef_id}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-7-retrieve-acl-with-service-id-not-valid","title":"Test Case 7: Retrieve ACL with service-id not valid","text":"<p>Test ID: capif_api_acl-7</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF if service-api-id is not valid</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${NOT_VALID_SERVICE_API_ID}?aef-id=${aef_id}</li> <li>Use NOT_VALID_SERVICE_API_ID and aef_id</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {service_api_id}, aef_id: {aef_id}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-8-retrieve-acl-with-service-api-id-and-aef-id-not-valid","title":"Test Case 8: Retrieve ACL with service-api-id and aef-id not valid","text":"<p>Test ID: capif_api_acl-8</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF if service-api-id and aef-id are not valid</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${NOT_VALID_SERVICE_API_ID}?aef-id=${AEF_ID_NOT_VALID}</li> <li>Use NOT_VALID_SERVICE_API_ID and aef_id</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-9-retrieve-acl-without-securitycontext-created-previously-by-invoker","title":"Test Case 9: Retrieve ACL without SecurityContext created previously by Invoker","text":"<p>Test ID: capif_api_acl-9</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL if no invoker had requested Security Context to CAPIF</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker created but no Security Context for Service API published had been requested.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li> <p>Discover published APIs</p> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-10-retrieve-acl-filtered-by-api-invoker-id-not-present","title":"Test Case 10: Retrieve ACL filtered by api-invoker-id not present","text":"<p>Test ID: capif_api_acl-10</p> <p>Description:</p> <p>This test case will check that an API Provider get not found response if filter by not valid api-invoker-id doesn't match any registered ACL.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={NOT_VALID_API_INVOKER_ID}</li> <li>Use serviceApiId, aefId and NOT_VALID_API_INVOKER_ID</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-11-retrieve-acl-with-apf-certificate","title":"Test Case 11: Retrieve ACL with APF Certificate","text":"<p>Test ID: capif_api_acl-11</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF using APF Certificate</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use APF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-12-retrieve-acl-with-amf-certificate","title":"Test Case 12: Retrieve ACL with AMF Certificate","text":"<p>Test ID: capif_api_acl-12</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF using AMF Certificate</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AMF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-13-retrieve-acl-with-invoker-certificate","title":"Test Case 13: Retrieve ACL with Invoker Certificate","text":"<p>Test ID: capif_api_acl-13</p> <p>Description:</p> <p>This test case will check that an API Provider can't retrieve ACL from CAPIF using Invoker Certificate</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_access_control_policy/#test-case-14-no-acl-for-invoker-after-be-removed","title":"Test Case 14: No ACL for invoker after be removed","text":"<p>Test ID: capif_api_acl-14</p> <p>Description:</p> <p>This test case will check that ACLs are removed after invoker is removed.</p> <p>Pre-Conditions:</p> <ul> <li>API Provider had a Service API Published on CAPIF</li> <li>API Invoker had a Security Context for Service API published and ACL is present</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Provider at CCF.</li> <li>Publish a provider API with name service_1</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Provider Get ACL information of invoker.</li> <li>Remove Invoker from CAPIF.</li> <li>Provider Get ACL information of invoker.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform Invoker Onboarding store apiInvokerId</p> </li> <li>Discover published APIs</li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Provider Retrieve ACL</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={api-invoker-id}</li> <li>Use serviceApiId, aefId and api-invoker-id</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li>Remove Invoker from CAPIF</li> <li>Provider Retrieve ACL<ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}&api-invoker-id={api-invoker-id}</li> <li>Use serviceApiId, aefId and api-invoker-id</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result: 1. ACL Response: 1. 200 OK Response. 2. body returned must accomplish AccessControlPolicyList data structure. 3. apiInvokerPolicies must: 1. contain only one object. 2. apiInvokerId must match apiInvokerId registered previously.</p> <ol> <li>ACL Response:<ol> <li>404 Not Found Response.</li> <li>body returned must accomplish Problem Details data structure.</li> <li>apiInvokerPolicies must:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"No ACLs found for the requested service: {NOT_VALID_SERVICE_API_ID}, aef_id: {AEF_ID_NOT_VALID}, invoker: None and supportedFeatures: None\".</li> <li>cause with message \"Wrong id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/","title":"Test Plan for CAPIF Api Auditing Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_auditing_service/#test-case-1-get-capif-log-entry","title":"Test Case 1: Get CAPIF Log Entry.","text":"<p>Test ID: capif_api_auditing-1</p> <p>Description:</p> <p>This test case will check that a CAPIF AMF can get log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>200 OK</li> <li>Response Body must follow InvocationLog data structure with:<ul> <li>aefId</li> <li>apiInvokerId</li> <li>logs</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-2-get-capif-log-entry-with-no-log-entry-in-capif","title":"Test Case 2: Get CAPIF Log Entry With no Log entry in CAPIF.","text":"<p>Test ID: capif_api_auditing-2</p> <p>Description:</p> <p>This test case will check that a CAPIF AEF can create log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found Log Entry in CAPIF\".</li> <li>cause with message \"Not Exist Logs with the filters applied\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-3-get-capif-log-entry-without-aef-id-and-api-invoker-id","title":"Test Case 3: Get CAPIF Log Entry without aef-id and api-invoker-id.","text":"<p>Test ID: capif_api_auditing-3</p> <p>Description:</p> <p>This test case will check that a CAPIF AEF can create log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is no pre-authorised (has no valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>400 Bad Request</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 400</li> <li>title with message \"Bad Request\"</li> <li>detail with message \"aef_id and api_invoker_id parameters are mandatory\".</li> <li>cause with message \"Mandatory parameters missing\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-4-get-capif-log-entry-with-filtter-api-version","title":"Test Case 4: Get CAPIF Log Entry with filtter api-version.","text":"<p>Test ID: capif_api_auditing-4</p> <p>Description:</p> <p>This test case will check that a CAPIF AMF can get log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}&api-version={v1}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>200 OK</li> <li>Response Body must follow InvocationLog data structure with:<ul> <li>aefId</li> <li>apiInvokerId</li> <li>logs</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_auditing_service/#test-case-5-get-capif-log-entry-with-filter-api-version-but-not-exist-in-log-entry","title":"Test Case 5: Get CAPIF Log Entry with filter api-version but not exist in log entry.","text":"<p>Test ID: capif_api_auditing-4</p> <p>Description:</p> <p>This test case will check that a CAPIF AMF can get log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> <li>Log Entry exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry 4. Get Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding, invoker onboarding </p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Create Log Entry:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Get Log:</p> <ol> <li>Send GET to https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}&api-version={v58}</li> <li>Use AMF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>detail with message \"Parameters do not match any log entry\"</li> <li>cause with message \"No logs found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/","title":"Test Plan for CAPIF Discover Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_discover_service/#test-case-1-discover-published-service-apis-by-authorised-api-invoker","title":"Test Case 1: Discover Published service APIs by Authorised API Invoker","text":"<p>Test ID: capif_api_discover_service-1</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs:<ul> <li>Send GET to https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains the API Published previously</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-2-discover-published-service-apis-by-non-authorised-api-invoker","title":"Test Case 2: Discover Published service APIs by Non Authorised API Invoker","text":"<p>Test ID: capif_api_discover_service-2</p> <p>Description:</p> <p>This test case will check that an API Publisher can't discover published APIs because is not authorized.</p> <p>Pre-Conditions:</p> <ul> <li>Service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by no invoker entity</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs by no invoker entity:<ul> <li>Send GET to https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Use not Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Discover Request By no invoker entity:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-3-discover-published-service-apis-by-not-registered-api-invoker","title":"Test Case 3: Discover Published service APIs by not registered API Invoker","text":"<p>Test ID: capif_api_discover_service-3</p> <p>Description:</p> <p>This test case will check that a not registered invoker is forbidden to discover published APIs.</p> <p>Pre-Conditions:</p> <ul> <li>Service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Publisher</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs with not valid apiInvoker:<ul> <li>Send GET to https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={INVOKER_NOT_REGISTERED}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Discover Request By Invoker:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"API Invoker does not exist\".</li> <li>cause with message \"API Invoker id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-4-discover-published-service-apis-by-registered-api-invoker-with-1-result-filtered","title":"Test Case 4: Discover Published service APIs by registered API Invoker with 1 result filtered","text":"<p>Test ID: capif_api_discover_service-4</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>At least 2 Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 and service_2 at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Discover filtered by api-name service_1 Service APIs by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs filtering by api-name:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}&api-name=service_1**</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> <li>filter by api-name service_1</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Publish request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains previously registered Service APIs published.</li> </ul> </li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains only Service API published with api-name service_1</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-5-discover-published-service-apis-by-registered-api-invoker-filtered-with-no-match","title":"Test Case 5: Discover Published service APIs by registered API Invoker filtered with no match","text":"<p>Test ID: capif_api_discover_service-5</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>At least 2 Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 and service_2 at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Discover filtered by api-name not published Service APIs by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs filtering by api-name not published:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}&api-name=NOT_VALID_NAME</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> <li>filter by api-name NOT_VALID_NAME</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Publish request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains previously registered Service APIs published.</li> </ul> </li> </ol> </li> <li>Response to Discover Request By Invoker:<ol> <li>404 Not Found response.</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"API Invoker {api_invoker_id} has no API Published that accomplish filter conditions\".</li> <li>cause with message \"No API Published accomplish filter conditions\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_discover_service/#test-case-6-discover-published-service-apis-by-registered-api-invoker-not-filtered","title":"Test Case 6: Discover Published service APIs by registered API Invoker not filtered","text":"<p>Test ID: capif_api_discover_service-6</p> <p>Description:</p> <p>This test case will check if Network App (Invoker) can discover published service APIs.</p> <p>Pre-Conditions:</p> <ul> <li>2 Service APIs are published.</li> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 and service_2 at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Discover without filter by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs not filtered:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Discover Request By Invoker:</p> <ol> <li>200 OK response.</li> <li>Response body must follow DiscoveredAPIs data structure:<ul> <li>Check if DiscoveredAPIs contains the 2 previously registered Service APIs published.</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/","title":"Test Plan for CAPIF Api Events Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_events_service/#test-case-1-creates-a-new-individual-capif-event-subscription","title":"Test Case 1: Creates a new individual CAPIF Event Subscription.","text":"<p>Test ID: capif_api_events-1</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) can Subscribe to Events</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-2-creates-a-new-individual-capif-event-subscription-with-invalid-subscriberid","title":"Test Case 2: Creates a new individual CAPIF Event Subscription with Invalid SubscriberId","text":"<p>Test ID: capif_api_events-2</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) cannot Subscribe to Events without valid SubcriberId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is not pre-authorised (has invalid InvokerId or apfId)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{SUBSCRIBER_NOT_REGISTERED}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker or APF or AEF or AMF Not found\".</li> <li>cause with message \"Subscriber Not Found\".</li> </ul> </li> </ol> </li> <li> <p>Event Subscriptions are not stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-3-deletes-an-individual-capif-event-subscription","title":"Test Case 3: Deletes an individual CAPIF Event Subscription","text":"<p>Test ID: capif_api_events-3</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) can Delete an Event Subscription</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> <li>Remove Event Subscription</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Remove Event Subscription:</p> <ol> <li>Send DELETE to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> <li> <p>Remove Event Subscription:</p> <ol> <li>204 No Content</li> </ol> </li> <li> <p>Event Subscription is not present at CAPIF Database.</p> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-4-deletes-an-individual-capif-event-subscription-with-invalid-subscriberid","title":"Test Case 4: Deletes an individual CAPIF Event Subscription with invalid SubscriberId","text":"<p>Test ID: capif_api_events-4</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) cannot Delete to Events without valid SubcriberId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId).</li> <li>CAPIF subscriber is subscribed to Events.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve Location Header with subscriptionId.</li> <li>Remove Event Subscribed with not valid Subscriber.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Remove Event Subcription with not valid subscriber:</p> <ol> <li>Send DELETE to https://{CAPIF_HOSTNAME}/capif-events/v1/{SUBSCRIBER_ID_NOT_VALID}/subscriptions/{subcriptionId}</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> <li> <p>Error Response Body must accomplish with ProblemDetails data structure with:</p> <ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker or APF or AEF or AMF Not found\".</li> <li>cause with message \"Subscriber Not Found\".</li> </ul> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-5-deletes-an-individual-capif-event-subscription-with-invalid-subscriptionid","title":"Test Case 5: Deletes an individual CAPIF Event Subscription with invalid SubscriptionId","text":"<p>Test ID: capif_api_events-5</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (Invoker or Publisher) cannot Delete an Event Subscription without valid SubscriptionId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has invalid InvokerId or apfId).</li> <li>CAPIF subscriber is subscribed to Events.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Subscribe to Events</li> <li>Retrieve Location Header with subscriptionId.</li> <li>Remove Event Subscribed with not valid Subscriber.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Event Subscription:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body</li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Remove Event Subcription with not valid subscriber:</p> <ol> <li>Send DELETE to to https://{CAPIF_HOSTNAME}/capif-events/v1/{subcriberId}/subscriptions/{SUBSCRIPTION_ID_NOT_VALID}</li> <li>Use Invoker Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Event Subscriptions are stored in CAPIF Database</p> </li> <li>Remove Event Subscription with not valid subscriber:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>detail with message \"Service API not existing\".</li> <li>cause with message \"Event API subscription id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-6-invoker-receives-service-api-invocation-events","title":"Test Case 6: Invoker receives Service API Invocation events","text":"<p>Test ID: capif_api_events-6, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE, receive the notification when AEF Send TO logging service result of invocations to their APIs.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered and published APIs.</li> <li>API Provider had a Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register provider and publish one API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover published APIs and extract apiIds and apiNames</li> <li>Subscribe to SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE event filtering by aefId.</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> <li>Emulate Success and Failure on API invocation of provider by Invoker, using Invocation Logs API.</li> </ol> <p>Information of Test:</p> <ol> <li>Perform provider registration</li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform invoker onboarding</p> </li> <li> <p>Discover published APIs:</p> <ul> <li>Get Api Ids And Api Names from response.</li> </ul> </li> <li> <p>Event Subscription to SERVICE_API_INVOCATION_SUCCESS and SERVICE_API_INVOCATION_FAILURE of provider previously registered:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['SERVICE_API_INVOCATION_SUCCESS','SERVICE_API_INVOCATION_FAILURE']</li> <li>eventFilter: only receive events from provider's aefId.</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Create Log Entry emulating provider receive Success and Failure api invocation from invoker:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body with:<ol> <li>aefId from provider published.</li> <li>apiInvokerId from invoker onboarded.</li> <li>apiId of published API</li> <li>apiName of published API</li> <li>200 and 400 results in two logs.</li> </ol> </li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Response to creation of log entry on CCF must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/api-invocation-logs/{apiVersion}/{aefId}/subscriptions/{logId}</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>Two Events have been received.</li> <li>Validate received events follow EventNotification data structure, with invocationLog in eventDetail parameter.<ol> <li>One should be SERVICE_API_INVOCATION_SUCCESS related with 200 result at Log.</li> <li>The other one must be SERVICE_API_INVOCATION_FAILURE related with 400 result at Log.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-7-invoker-subscribe-to-service-api-available-and-unavailable-events","title":"Test Case 7: Invoker subscribe to Service API Available and Unavailable events","text":"<p>Test ID: capif_api_events-7, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE, receive the notification when AEF publish and remove it. </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered and published APIs.</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register provider and publish one API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover published APIs and extract apiIds and apiNames</li> <li>Subscribe to SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE event filtering by aefId.</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header</li> <li>Provider publish new API.</li> <li>Provider remove published API.</li> </ol> <p>Information of Test:</p> <ol> <li>Perform provider registration</li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Perform invoker onboarding</p> </li> <li> <p>Discover published APIs:</p> <ul> <li>Get Api Ids And Api Names from response.</li> </ul> </li> <li> <p>Event Subscription to SERVICE_API_AVAILABLE and SERVICE_API_UNAVAILABLE of provider previously registered:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['SERVICE_API_AVAILABLE','SERVICE_API_UNAVAILABLE']</li> <li>eventFilter: only receive events from provider's aefId.</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Publish new Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_2</li> <li>Store serviceApiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Remove published Service API at CCF:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Event Subscription must accomplish:</p> <ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li> <p>Mock Server received messages must accomplish:</p> <ol> <li>Two Events have been received.</li> <li>Validate received events follow EventNotification data structure, with apiIds in eventDetail parameter.<ol> <li>One should be SERVICE_API_AVAILABLE apiId of service_2 published API.</li> <li>The other one must be SERVICE_API_UNAVAILABLE apiId of service_1 published API.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-8-invoker-subscribe-to-service-api-update","title":"Test Case 8: Invoker subscribe to Service API Update","text":"<p>Test ID: capif_api_events-8, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to SERVICE_API_UPDATE, receive the notification when AEF Update some information on API Published.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered and published APIs.</li> <li>API Provider had a Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider and publish one API at CCF</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Discover published APIs and extract apiIds and apiNames</li> <li>Subscribe to SERVICE_API_UPDATE event filtering by aefId.</li> <li>Retrieve {subscriberId} and {subscriptionId} from Location Header at event subscription</li> <li>Provider update information of Service API Published.</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> <li>Store serviceApiId</li> </ul> </li> <li> <p>Perform invoker onboarding</p> </li> <li> <p>Discover published APIs:</p> <ul> <li>Get Api Ids And Api Names from response.</li> </ul> </li> <li> <p>Event Subscription to SERVICE_API_UPDATE of provider previously registered:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['SERVICE_API_UPDATE']</li> <li>eventFilter: only receive events from provider's aefId.</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>body [service api description] with overrided apiName to service_1_modified**</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Response to Update Published Service API:<ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1_modified**</li> </ul> </li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>One Event has been received.</li> <li>Validate received events follow EventNotification data structure, with serviceAPIDescriptions in eventDetail parameter.<ol> <li>Event should be SERVICE_API_UPDATE with eventDetail with modified apiName.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-9-provider-subscribe-to-api-invoker-events","title":"Test Case 9: Provider subscribe to API Invoker events","text":"<p>Test ID: capif_api_events-9, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Provider subscribed to API Invoker events (API_INVOKER_ONBOARDED, API_INVOKER_UPDATED and API_INVOKER_OFFBOARDED), receive the notifications when Invoker is onboarded, updated and removed respectively.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Subscribe Provider to API_INVOKER_ONBOARDED, API_INVOKER_UPDATED and API_INVOKER_OFFBOARDED events.</li> <li>Register Invoker and Onboard Invoker at CCF</li> <li>Update Onboarding Information at CCF with a minor change on \"notificationDestination\"</li> <li>Offboard Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Event Subscription to API_INVOKER_ONBOARDED, API_INVOKER_UPDATED and API_INVOKER_OFFBOARDED events:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['API_INVOKER_ONBOARDED', 'API_INVOKER_UPDATED', 'API_INVOKER_OFFBOARDED']</li> </ol> </li> <li>Use Provider AMF Certificate</li> </ol> </li> <li>Perform invoker onboarding</li> <li>Update information of previously onboarded Invoker:<ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>\"notificationDestination\": \"http://host.docker.internal:8086/netapp_new_callback\",</li> </ul> </li> <li>Offboard:<ul> <li>Send DELETE to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Update Request (PUT) with minor change must contain:<ol> <li>200 OK response.</li> <li>notificationDestination on response must contain the new value</li> </ol> </li> <li>Response to Offboard Request (DELETE) must contain:<ol> <li>204 No Content</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>Three Events have been received.</li> <li>Validate received events follow EventNotification data structure, with apiInvokerIds in eventDetail parameter.<ol> <li>One Event should be API_INVOKER_ONBOARDED with eventDetail with modified apiInvokerId.</li> <li>One Event should be API_INVOKER_UPDATED with eventDetail with modified apiInvokerId.</li> <li>One Event should be API_INVOKER_OFFBOARDED with eventDetail with modified apiInvokerId.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-10-provider-subscribed-to-acl-update-event","title":"Test Case 10: Provider subscribed to ACL update event","text":"<p>Test ID: capif_api_events-10, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Provider subscribed to ACCESS_CONTROL_POLICY_UPDATE receive a notification when ACL Changes.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>API Provider had one Service API Published on CAPIF</li> <li>API Invoker had a Security Context for the Service API published by provider.</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF.</li> <li>Publish a provider API with name service_1.</li> <li>Register Invoker and Onboard Invoker at CCF.</li> <li>Subscribe Provider to ACCESS_CONTROL_POLICY_UPDATE event.</li> <li>Discover APIs filtered by aef_id</li> <li>Create Security Context for Invoker.</li> <li>Provider Retrieve ACL</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Perform invoker onboarding</li> <li>Event Subscription to ACCESS_CONTROL_POLICY_UPDATE event:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['ACCESS_CONTROL_POLICY_UPDATE']</li> <li>eventFilters: apiInvokerIds array with apiInvokerId of invoker</li> </ol> </li> <li>Use Provider AMF Certificate</li> </ol> </li> <li>Discover published APIs</li> <li>Create Security Context for Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li>Provider Retrieve ACL<ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain only one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>One Event has been received.</li> <li>Validate received event follow EventNotification data structure, with accCtrlPolListExt in eventDetail parameter.<ol> <li>One Event should be ACCESS_CONTROL_POLICY_UPDATE with eventDetail with accCtrlPolListExt including the apiId and apiInvokerPolicies.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-11-provider-receives-an-acl-unavailable-event-when-invoker-remove-security-context","title":"Test Case 11: Provider receives an ACL unavailable event when invoker remove Security Context.","text":"<p>Test ID: capif_api_events-11, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to ACCESS_CONTROL_POLICY_UNAVAILABLE will receive the notification when AEF remove Security Context created previously.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>API Provider had one Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF.</li> <li>Publish a provider API with name service_1.</li> <li>Register Invoker and Onboard Invoker at CCF.</li> <li>Subscribe Invoker to ACCESS_CONTROL_POLICY_UNAVAILABLE event.</li> <li>Discover APIs filtered by aef_id</li> <li>Create Security Context for Invoker.</li> <li>Provider Retrieve ACL.</li> <li>Remove Security Context for Invoker.</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Perform invoker onboarding</li> <li>Event Subscription to ACCESS_CONTROL_POLICY_UNAVAILABLE event:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['ACCESS_CONTROL_POLICY_UNAVAILABLE']</li> <li>eventFilters: apiInvokerIds array with apiInvokerId of invoker</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li>Discover published APIs</li> <li>Create Security Context for Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li>Provider Retrieve ACL<ul> <li>Send GET https://{CAPIF_HOSTNAME}/access-control-policy/v1/accessControlPolicyList/${serviceApiId}?aef-id=${aef_id}</li> <li>Use serviceApiId and aefId</li> <li>Use AEF Provider Certificate</li> </ul> </li> <li>Delete Security Context of Invoker by Provider:<ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Use AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> <li>ACL Response:<ol> <li>200 OK Response.</li> <li>body returned must accomplish AccessControlPolicyList data structure.</li> <li>apiInvokerPolicies must:<ol> <li>contain only one object.</li> <li>apiInvokerId must match apiInvokerId registered previously.</li> </ol> </li> </ol> </li> <li>Delete security context:<ol> <li>204 No Content response.</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>One Event has been received.</li> <li>Validate received event follow EventNotification data structure, without eventDetail parameter.<ol> <li>One Event should be ACCESS_CONTROL_POLICY_UNAVAILABLE without eventDetail.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_events_service/#test-case-12-invoker-receives-an-invoker-authorization-revoked-and-acl-unavailable-event-when-provider-revoke-invoker-authorization","title":"Test Case 12: Invoker receives an Invoker Authorization Revoked and ACL unavailable event when Provider revoke Invoker Authorization.","text":"<p>Test ID: capif_api_events-12, mockserver</p> <p>Description:</p> <p>This test case will check that a CAPIF Invoker subscribed to API_INVOKER_AUTHORIZATION_REVOKED and ACCESS_CONTROL_POLICY_UNAVAILABLE receive both notification when AEF revoke invoker's authorization.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid InvokerId or apfId from CAPIF Authority)</li> <li>CAPIF provider is correctly registered.</li> <li>API Provider had one Service API Published on CAPIF</li> <li>Mock Server is up and running to receive requests.</li> <li>Mock Server is clean.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF.</li> <li>Publish a provider API with name service_1.</li> <li>Register Invoker and Onboard Invoker at CCF.</li> <li>Subscribe Invoker to ACCESS_CONTROL_POLICY_UNAVAILABLE and API_INVOKER_AUTHORIZATION_REVOKED events.</li> <li>Discover APIs filtered by aef_id</li> <li>Create Security Context for Invoker.</li> <li>Revoke Authorization by Provider.</li> </ol> <p>Information of Test:</p> <ol> <li>Check and Clean Mock Server</li> <li>Perform provider registration</li> <li>Perform invoker onboarding</li> <li>Event Subscription to ACCESS_CONTROL_POLICY_UNAVAILABLE and API_INVOKER_AUTHORIZATION_REVOKED event:<ol> <li>Send POST to https://{CAPIF_HOSTNAME}/capif-events/v1/{subscriberId}/subscriptions</li> <li>body event subscription request body with:<ol> <li>events: ['ACCESS_CONTROL_POLICY_UNAVAILABLE','API_INVOKER_AUTHORIZATION_REVOKED']</li> <li>eventFilters: apiInvokerIds array with apiInvokerId of invoker</li> </ol> </li> <li>Use Invoker Certificate</li> </ol> </li> <li>Discover published APIs</li> <li>Create Security Context for Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> <li>Revoke Authorization by Provider:<ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/delete</li> <li>body security notification body</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Event Subscription must accomplish:<ol> <li>201 Created</li> <li>The URI of the created resource shall be returned in the \"Location\" HTTP header, following this structure: {apiRoot}/capif-events/{apiVersion}/{subscriberId}/subscriptions/{subscriptionId}</li> <li>Response Body must follow EventSubscription data structure.</li> </ol> </li> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> <li>Revoke Authorization:<ol> <li>204 No Content response.</li> </ol> </li> <li>Mock Server received messages must accomplish:<ol> <li>Two Events has been received.</li> <li>Validate received event follow EventNotification data structure, without eventDetail parameter.<ol> <li>One Event should be ACCESS_CONTROL_POLICY_UNAVAILABLE without eventDetail.</li> <li>One Event should be API_INVOKER_AUTHORIZATION_REVOKED without eventDetail.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/","title":"Test Plan for CAPIF Api Invoker Management","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_invoker_management/#test-case-1-onboard-network-app","title":"Test Case 1: Onboard Network App","text":"<p>Test ID: capif_api_invoker_management-1</p> <p>Description:</p> <p>This test will try to register new Network App at CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was not registered previously</li> <li>Network App was not onboarded previously</li> <li>Preconditions: The administrator must have previously registered the User.</li> </ul> <p>Execution Steps:</p> <ol> <li>Retrieve access_token by User from register</li> <li>Onboard Invoker at CCF</li> <li>Store signed Certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at invoker</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Onboard Invoker:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers</li> <li>Reference Request Body: invoker onboarding body</li> <li>\"onboardingInformation\"->\"apiInvokerPublicKey\": must contain public key generated by Invoker.</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-2-onboard-network-app-already-onboarded","title":"Test Case 2: Onboard Network App Already onboarded","text":"<p>Test ID: capif_api_invoker_management-2</p> <p>Description:</p> <p>This test will check second onboard of same Network App is not allowed.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Network App at CCF</li> <li>Onboard Network App at CCF</li> <li>Store signed Certificate at Network App</li> <li>Onboard Again the Network App at CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Repeat Onboard Invoker:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers</li> <li>Reference Request Body: invoker onboarding body</li> <li>\"onboardingInformation\"->\"apiInvokerPublicKey\": must contain public key generated by Invoker.</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Second Onboard of Network App must accomplish:<ol> <li>403 Forbidden</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 403</li> <li>title with message \"Forbidden\"</li> <li>detail with message \"Invoker Already registered\".</li> <li>cause with message \"Identical invoker public key\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-3-update-onboarded-network-app","title":"Test Case 3: Update Onboarded Network App","text":"<p>Test ID: capif_api_invoker_management-3</p> <p>Description:</p> <p>This test will try to update information of previous onboard Network App at CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Update Onboarding Information at CCF with a minor change on \"notificationDestination\"</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Update information of previously onboarded Invoker:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>\"notificationDestination\": \"http://host.docker.internal:8086/netapp_new_callback\",</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Update Request (PUT) with minor change must contain:<ol> <li>200 OK response.</li> <li>notificationDestination on response must contain the new value</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-4-update-not-onboarded-network-app","title":"Test Case 4: Update Not Onboarded Network App","text":"<p>Test ID: capif_api_invoker_management-4</p> <p>Description:</p> <p>This test will try to update information of not onboarded Network App at CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was not onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Update Onboarding Information at CCF of not onboarded</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Update information of not onboarded Invoker:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{INVOKER_NOT_REGISTERED}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> </ol> </li> <li>Response to Update Request (PUT) must contain:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Please provide an existing Network App ID\".</li> <li>cause with message \"Not exist Network App ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-5-offboard-network-app","title":"Test Case 5: Offboard Network App","text":"<p>Test ID: capif_api_invoker_management-5</p> <p>Description:</p> <p>This test case will check that a Registered Network App can be deleted.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Offboard Invoker at CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Offboard:</p> <ul> <li>Send DELETE to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> </ol> </li> <li>Response to Offboard Request (DELETE) must contain:<ol> <li>204 No Content</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-6-offboard-not-previsouly-onboarded-network-app","title":"Test Case 6: Offboard Not previsouly Onboarded Network App","text":"<p>Test ID: capif_api_invoker_management-6</p> <p>Description:</p> <p>This test case will check that a Non-Registered Network App cannot be deleted</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was not onboarded previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Offboard Invoker at CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Offboard:</p> <ul> <li>Send DELETE to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{INVOKER_NOT_REGISTERED}</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Offboard Request (DELETE) must contain:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Please provide an existing Network App ID\".</li> <li>cause with message \"Not exist Network App ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_invoker_management/#test-case-7-update-onboarded-network-app-certificate","title":"Test Case 7: Update Onboarded Network App Certificate","text":"<p>Test ID: capif_api_invoker_management-7</p> <p>Description:</p> <p>This test will try to update public key and get a new signed certificate by CAPIF Core.</p> <p>Pre-Conditions:</p> <ul> <li>Network App was registered previously</li> <li>Network App was onboarded previously with {onboardingId} and {public_key_1}</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Invoker at CCF</li> <li>Onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Update Onboarding Information at CCF with new public key</li> <li>Update Onboarding Information at CCF with minor change</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding with public_key_1.</p> </li> <li> <p>Create {public_key_2}</p> </li> <li> <p>Update information of previously onboarded Invoker:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>[\"onboardingInformation\"][\"apiInvokerPublicKey\"]: {public_key_2},</li> <li>Store new certificate.</li> </ul> </li> <li> <p>Update information of previously onboarded Invoker Using new certificate:</p> <ul> <li>Send PUT to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers/{onboardingId}</li> <li>Reference Request Body is: [put invoker onboarding body]</li> <li>\"notificationDestination\": \"http://host.docker.internal:8086/netapp_new_callback\",</li> <li>Use new Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Onboard request must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> <li>Response to Update Request (PUT) with new public key:<ol> <li>200 OK response.</li> <li>apiInvokerCertificate with new certificate on response -> store to use.</li> </ol> </li> <li>Response to Update Request (PUT) with minor change must contain:<ol> <li>200 OK response.</li> <li>notificationDestination on response must contain the new value</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/","title":"Test Plan for CAPIF Api Logging Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_logging_service/#test-case-1-creates-a-new-individual-capif-log-entry","title":"Test Case 1: Creates a new individual CAPIF Log Entry.","text":"<p>Test ID: capif_api_logging-1</p> <p>Description:</p> <p>This test case will check that a CAPIF AEF can create log entry to Logging Service</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid aefId from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>201 Created</li> <li>Response Body must follow InvocationLog data structure with:<ul> <li>aefId</li> <li>apiInvokerId</li> <li>logs</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invocation-logs/v1/{aefId}/logs/{logId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-2-creates-a-new-individual-capif-log-entry-with-invalid-aefid","title":"Test Case 2: Creates a new individual CAPIF Log Entry with Invalid aefId","text":"<p>Test ID: capif_api_logging-2</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is not pre-authorised (has not valid aefId from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{not-valid-aefId}/logs</li> <li>body log entry request body</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Exposer not exist\".</li> <li>cause with message \"Exposer id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-3-creates-a-new-individual-capif-log-entry-with-invalid-serviceapi","title":"Test Case 3: Creates a new individual CAPIF Log Entry with Invalid serviceAPI","text":"<p>Test ID: capif_api_logging-3</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid aefId from CAPIF Authority)</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body [log entry request body with serviceAPI apiName apiId not valid]</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not exist\".</li> <li>cause with message \"Invoker id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-4-creates-a-new-individual-capif-log-entry-with-invalid-apiinvokerid","title":"Test Case 4: Creates a new individual CAPIF Log Entry with Invalid apiInvokerId","text":"<p>Test ID: capif_api_logging-4</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid aefId from CAPIF Authority)</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body [log entry request body with invokerId not valid]</li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>For each apiProvFuncs, we must check:<ol> <li>apiProvFuncId is set</li> <li>apiProvCert under regInfo is set properly</li> </ol> </li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> <li> <p>Response to Logging Service must accomplish:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not exist\".</li> <li>cause with message \"Invoker id not found\".</li> </ul> </li> </ol> </li> <li> <p>Log Entry are not stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_logging_service/#test-case-5-creates-a-new-individual-capif-log-entry-with-invalid-aefid-in-body","title":"Test Case 5: Creates a new individual CAPIF Log Entry with Invalid aefId in body","text":"<p>Test ID: capif_api_logging-5</p> <p>Description:</p> <p>This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId in body</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF provider is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>Service exist in CAPIF</li> <li>Invoker exist in CAPIF</li> </ul> <p>Execution Steps: 1. Register Provider and Invoker CCF 2. Publish Service 3. Create Log Entry</p> <p>Information of Test:</p> <ol> <li> <p>Perform provider onboarding and invoker onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Log Entry:</p> <ol> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs</li> <li>body [log entry request body with bad aefId] </li> <li>Use AEF Certificate</li> </ol> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Logging Service must accomplish:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"AEF id not matching in request and body\".</li> <li>cause with message \"Not identical AEF id\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/","title":"Test Plan for CAPIF Api Provider Management","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_provider_management/#test-case-1-register-api-provider","title":"Test Case 1: Register Api Provider","text":"<p>Test ID: capif_api_provider_management-1</p> <p>Description:</p> <p>This test case will check that Api Provider can be registered con CCF</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid certificate from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Create private and public key for provider and each function to register.</li> <li>Register Provider.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Register Provider at Provider Management:<ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>For each apiProvFuncs, we must check:<ol> <li>apiProvFuncId is set</li> <li>apiProvCert under regInfo is set properly</li> </ol> </li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-2-register-api-provider-already-registered","title":"Test Case 2: Register Api Provider Already registered","text":"<p>Test ID: capif_api_provider_management-2</p> <p>Description:</p> <p>This test case will check that a Api Provider previously registered cannot be re-registered</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously and there is a {registerId} for his Api Provider in the DB</li> </ul> <p>Execution Steps:</p> <ol> <li>Create private and public key for provider and each function to register.</li> <li>Register Provider.</li> <li>Re-Register Provider.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Re-Register Provider:</p> <ul> <li>Same regSec than Previous registration</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Re-Register Provider:<ol> <li>403 Forbidden response.</li> <li> <p>body returned must accomplish ProblemDetails data structure, with:</p> <ul> <li>status 403</li> <li>title with message \"Forbidden\"</li> <li>detail with message \"Provider already registered\".</li> <li>cause with message \"Identical provider reg sec\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-3-update-registered-api-provider","title":"Test Case 3: Update Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-3</p> <p>Description:</p> <p>This test case will check that a Registered Api Provider can be updated</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously and there is a {registerId} for his Api Provider in the DB</li> </ul> <p>Execution Steps:</p> <ol> <li>Create private and public key for provider and each function to register.</li> <li>Register Provider</li> <li>Update Provider</li> </ol> <p>Information of Test:</p> <ol> <li>Create public and private key at provider for provider itself and each function (apf, aef and amf)</li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Get Resource URL from Location</li> </ul> </li> <li> <p>Update Provider:</p> <ul> <li>Send PUT to Resource URL returned at registration https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{registrationId}</li> <li>body provider request body with apiProvDomInfo set to ROBOT_TESTING_MOD</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Register Provider:</p> <ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> <li> <p>Update Provider:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure, with:<ul> <li>apiProvDomInfo set to ROBOT_TESTING_MOD</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-4-update-not-registered-api-provider","title":"Test Case 4: Update Not Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-4</p> <p>Description:</p> <p>This test case will check that a Non-Registered Api Provider cannot be updated</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was not registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Update Not Registered Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Update Not Registered Provider:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{API_PROVIDER_NOT_REGISTERED}</li> <li>body provider request body</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Update Not Registered Provider:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Not Exist Provider Enrolment Details\".</li> <li>cause with message \"Not found registrations to Send THIS api provider details\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-5-partially-update-registered-api-provider","title":"Test Case 5: Partially Update Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-5</p> <p>Description:</p> <p>This test case will check that a Registered Api Provider can be partially updated</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously and there is a {registerId} for his Api Provider in the DB</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Register Provider</li> <li>Partial update provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Partial update provider:</p> <ul> <li>Send PATCH https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{registrationId}</li> <li>body provider request patch body</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Partial update provider at Provider Management:<ol> <li>200 OK response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure, with:<ul> <li>apiProvDomInfo with \"ROBOT_TESTING_MOD\"</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-6-partially-update-not-registered-api-provider","title":"Test Case 6: Partially Update Not Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-6</p> <p>Description:</p> <p>This test case will check that a Non-Registered Api Provider cannot be partially updated </p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was not registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Register Provider</li> <li>Partial update provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Partial update Provider:</p> <ul> <li>Send PATCH https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{API_API_PROVIDER_NOT_REGISTERED}</li> <li>body provider request patch body</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Partial update provider:<ol> <li>404 Not Found response.</li> <li> <p>body returned must accomplish ProblemDetails data structure, with:</p> <ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Not Exist Provider Enrolment Details\".</li> <li>cause with message \"Not found registrations to Send THIS api provider details\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-7-delete-registered-api-provider","title":"Test Case 7: Delete Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-7</p> <p>Description:</p> <p>This test case will check that a Registered Api Provider can be deleted</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Register Provider</li> <li>Delete Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Delete registered provider:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{registrationId}</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete Provider:<ol> <li>204 No Content response.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_provider_management/#test-case-8-delete-not-registered-api-provider","title":"Test Case 8: Delete Not Registered Api Provider","text":"<p>Test ID: capif_api_provider_management-8</p> <p>Description:</p> <p>This test case will check that a Non-Registered Api Provider cannot be deleted</p> <p>Pre-Conditions:</p> <ul> <li>Api Provider was not registered previously</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Delete Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Create public and private key at provider for provider itself and each function (apf, aef and amf)</p> </li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Authentication Bearer with access_token</li> <li>Store each cert in a file with according name.</li> </ul> </li> <li> <p>Delete registered provider at Provider Management:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations/{API_PROVIDER_NOT_REGISTERED}</li> <li>Use AMF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete Provider:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Not Exist Provider Enrolment Details\".</li> <li>cause with message \"Not found registrations to Send THIS api provider details\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/","title":"Test Plan for CAPIF Api Publish Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_publish_service/#test-case-1-publish-api-by-authorised-api-publisher","title":"Test Case 1: Publish API by Authorised API Publisher","text":"<p>Test ID: capif_api_publish_service-1</p> <p>Description:</p> <p>This test case will check that an API Publisher can Publish an API</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li> <p>Register Provider at CCF and store certificates.</p> </li> <li> <p>Publish Service API</p> </li> <li> <p>Retrieve {apiId} from body and Location header with new resource created from response</p> </li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> </li> <li> <p>Send POST to ccf_publish_url: https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</p> </li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Published Service API is stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-2-publish-api-by-non-authorised-api-publisher","title":"Test Case 2: Publish API by NON Authorised API Publisher","text":"<p>Test ID: capif_api_publish_service-2</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Publish an API withot valid apfId </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API with invalid APF ID</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API with invalid APF ID at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{APF_ID_NOT_VALID}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Publisher not existing\".</li> <li>cause with message \"Publisher id not found\".</li> </ul> </li> </ol> </li> <li> <p>Service API is NOT stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-3-retrieve-all-apis-published-by-authorised-apfid","title":"Test Case 3: Retrieve all APIs Published by Authorised apfId","text":"<p>Test ID: capif_api_publish_service-3</p> <p>Description:</p> <p>This test case will check that an API Publisher can Retrieve all API published</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>At least 2 service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API service_1</li> <li>Retrieve {apiId1} from body and Location header with new resource created from response</li> <li>Publish Service API service_2</li> <li>Retrieve {apiId2} from body and Location header with new resource created from response</li> <li>Retrieve All published APIs and check if both are present.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Publish Other Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve all published APIs:</p> <ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to service 1 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId1}</li> </ol> </li> <li> <p>Response to service 2 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId2}</li> </ol> </li> <li> <p>Published Service APIs are stored in CAPIF Database</p> </li> <li> <p>Response to Retrieve all published APIs:</p> <ol> <li>200 OK</li> <li>Response body must return an array of ServiceAPIDescription data.</li> <li>Array must contain all previously published APIs.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-4-retrieve-all-apis-published-by-non-authorised-apfid","title":"Test Case 4: Retrieve all APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-4</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Retrieve API published when apfId is not authorised </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Retrieve All published APIs</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Retrieve all published APIs:</p> <ul> <li>Send GET to https://{CAPIF_HOSTNAME}/published-apis/v1/{APF_ID_NOT_VALID}/service-apis</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>401 Non Authorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Provider not existing\".</li> <li>cause with message \"Provider id not found\".</li> </ul> </li> </ol> </li> <li> <p>Service API is NOT stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-5-retrieve-single-apis-published-by-authorised-apfid","title":"Test Case 5: Retrieve single APIs Published by Authorised apfId","text":"<p>Test ID: capif_api_publish_service-5</p> <p>Description:</p> <p>This test case will check that an API Publisher can Retrieve API published one by one</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>At least 2 service APIs are published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API service_1.</li> <li>Retrieve {apiId1} from body and Location header with new resource created from response.</li> <li>Publish Service API service_2.</li> <li>Retrieve {apiId2} from body and Location header with new resource created from response.</li> <li>Retrieve service_1 API Detail.</li> <li>Retrieve service_2 API Detail.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Publish Other Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_2</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve service_1 published APIs detail:</p> <ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{apiId1}</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve service_2 published APIs detail:</p> <ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{apiId2}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to service 1 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId1}</li> </ol> </li> <li> <p>Response to service 2 Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId2}</li> </ol> </li> <li> <p>Published Service APIs are stored in CAPIF Database</p> </li> <li> <p>Response to Retrieve service_1 published API using apiId1:</p> <ol> <li>200 OK</li> <li>Response body must return a ServiceAPIDescription data.</li> <li>Array must contain same information than service_1 published registration response.</li> </ol> </li> <li> <p>Response to Retrieve service_2 published API using apiId2:</p> <ol> <li>200 OK</li> <li>Response body must return a ServiceAPIDescription data.</li> <li>Array must contain same information than service_2 published registration response.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-6-retrieve-single-apis-non-published-by-authorised-apfid","title":"Test Case 6: Retrieve single APIs non Published by Authorised apfId","text":"<p>Test ID: capif_api_publish_service-6</p> <p>Description:</p> <p>This test case will check that an API Publisher try to get detail of not published api.</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>No published api</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Retrieve not published API Detail.</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration</li> <li>Retrieve not published APIs detail:<ul> <li>Send GET to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Retrieve for NOT published API must accomplish:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"No Service with specific credentials exists\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-7-retrieve-single-apis-published-by-non-authorised-apfid","title":"Test Case 7: Retrieve single APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-7</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Retrieve detailed API published when apfId is not authorised </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API at CCF</li> <li>Retrieve {apiId} from body and Location header with new resource created from response.</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Invoker Certificate</li> <li>Retrieve detailed published API acting as Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve detailed published APIs:</p> <ul> <li>Send GET to https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/${apiId}</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Retrieve Detailed published API acting as Invoker must accomplish:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> <li> <p>Service API is NOT stored in CAPIF Database</p> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-8-update-api-published-by-authorised-apfid-with-valid-serviceapiid","title":"Test Case 8: Update API Published by Authorised apfId with valid serviceApiId","text":"<p>Test ID: capif_api_publish_service-8</p> <p>Description:</p> <p>This test case will check that an API Publisher can Update published API with a valid serviceApiId </p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> <li>A service APIs is published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API</li> <li>Retrieve {apiId} from body and Location header with new resource url created from response</li> <li>Update published Service API.</li> <li>Retrieve detail of Service API</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>get resource url from location Header.</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>body service api description with overrided apiName to service_1_modified</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Retrieve detail of service API:</p> <ul> <li>Send GET to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>check apiName is service_1_modified</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Update Published Service API:</p> <ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1_modified</li> </ul> </li> </ol> </li> <li> <p>Response to Retrieve detail of Service API:</p> <ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1_modified.</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-9-update-apis-published-by-authorised-apfid-with-invalid-serviceapiid","title":"Test Case 9: Update APIs Published by Authorised apfId with invalid serviceApiId","text":"<p>Test ID: capif_api_publish_service-9</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Update published API with a invalid serviceApiId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Update published Service API.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>body service api description with overrided apiName to service_1*_modified*</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Response to Update Published Service API:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"Service API id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-10-update-apis-published-by-non-authorised-apfid","title":"Test Case 10: Update APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-10</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Update API published when apfId is not authorised</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is NOT pre-authorised (has invalid apfId from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API at CCF</li> <li>Retrieve {apiId} from body and Location header with new resource created from response.</li> <li>Register and onboard Invoker at CCF</li> <li>Store signed Invoker Certificate</li> <li>Update published API at CCF as Invoker</li> <li>Retrieve detail of Service API as publisher</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Update published API at CCF:</p> <ul> <li>Send PUT to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> <li>body service api description with overrided apiName to service_1*_modified*</li> <li>Use Invoker Certificate</li> </ul> </li> <li> <p>Retrieve detail of service API:</p> <ul> <li>Send GET to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>check apiName is service_1</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Update published API acting as Invoker must accomplish:</p> <ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> <li> <p>Response to Retrieve Detail of Service API:</p> <ol> <li>200 OK</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiName service_1.</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-11-delete-api-published-by-authorised-apfid-with-valid-serviceapiid","title":"Test Case 11: Delete API Published by Authorised apfId with valid serviceApiId","text":"<p>Test ID: capif_api_publish_service-11</p> <p>Description:</p> <p>This test case will check that an API Publisher can Delete published API with a valid serviceApiId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority).</li> <li>A service APIs is published.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Publish Service API</li> <li>Retrieve {apiId} from body and Location header with new resource created from response</li> <li>Remove published API at CCF</li> <li>Try to retreive deleted service API from CCF</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Remove published Service API at CCF:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> <li>Use APF Certificate</li> </ul> </li> <li>Retrieve detail of service API:<ul> <li>Send GET to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{serivceApiId}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Response to Publish request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow ServiceAPIDescription data structure with:<ul> <li>apiId</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/published-apis/v1/{apfId}/service-apis/{serviceApiId}</li> </ol> </li> <li> <p>Published Service API is stored in CAPIF Database</p> </li> <li> <p>Response to Remove published Service API at CCF:</p> <ol> <li>204 No Content</li> </ol> </li> <li> <p>Response to Retrieve for DELETED published API must accomplish:</p> <ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"No Service with specific credentials exists\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-12-delete-apis-published-by-authorised-apfid-with-invalid-serviceapiid","title":"Test Case 12: Delete APIs Published by Authorised apfId with invalid serviceApiId","text":"<p>Test ID: capif_api_publish_service-12</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Delete with invalid serviceApiId</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority).</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Remove published API at CCF with invalid serviceId</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Remove published Service API at CCF with invalid serviceId:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>Use APF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Remove published Service API at CCF:<ol> <li>404 Not Found</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Service API not found\".</li> <li>cause with message \"Service API id not found\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_publish_service/#test-case-13-delete-apis-published-by-non-authorised-apfid","title":"Test Case 13: Delete APIs Published by NON Authorised apfId","text":"<p>Test ID: capif_api_publish_service-12</p> <p>Description:</p> <p>This test case will check that an API Publisher cannot Delete API published when apfId is not authorised</p> <p>Pre-Conditions:</p> <ul> <li>CAPIF subscriber is pre-authorised (has valid apfId from CAPIF Authority).</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF and store certificates.</li> <li>Register Invoker and onboard Invoker at CCF</li> <li>Remove published API at CCF with invalid serviceId as Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body service api description with apiName service_1</li> <li>Get apiId</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Remove published Service API at CCF with invalid serviceId as Invoker:</p> <ul> <li>Send DELETE to resource URL https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis/{SERVICE_API_ID_NOT_VALID}</li> <li>Use Invoker Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Remove published Service API at CCF:<ol> <li>401 Unauthorized</li> <li>Error Response Body must accomplish with ProblemDetails data structure with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"User not authorized\".</li> <li>cause with message \"Certificate not authorized\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/","title":"Test Plan for CAPIF Api Security Service","text":"<p>At this documentation you will have all information and related files and examples of test plan for this API.</p>"},{"location":"testing/testplan/api_security_service/#test-case-1-create-a-security-context-for-an-api-invoker","title":"Test Case 1: Create a security context for an API invoker","text":"<p>Test ID: capif_security_api-1</p> <p>Description:</p> <p>This test case will check that an API Invoker can create a Security context</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Invoker Onboarding</li> <li>Create Security Context for this Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Create security context:<ol> <li>201 Created response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> <li>Location Header must contain the new resource URL {apiRoot}/capif-security/v1/trustedInvokers/{apiInvokerId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-2-create-a-security-context-for-an-api-invoker-with-provider-role","title":"Test Case 2: Create a security context for an API invoker with Provider role","text":"<p>Test ID:: capif_security_api-2</p> <p>Description:</p> <p>This test case will check that an Provider cannot create a Security context with valid apiInvokerId.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID), but user that create Security Context with Provider role</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker but using Provider certificate.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Create security context using Provider certificate:</p> <ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\".</li> </ul> </li> </ol> </li> <li> <p>No context stored at DB</p> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-3-create-a-security-context-for-an-api-invoker-with-provider-entity-role-and-invalid-apiinvokerid","title":"Test Case 3: Create a security context for an API invoker with Provider entity role and invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-3</p> <p>Description:</p> <p>This test case will check that an Provider cannot create a Security context with invalid apiInvokerID.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID), but user that create Security Context with Provider role</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Create Security Context for this not valid apiInvokerId and using Provider certificate.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Create security context using Provider certificate:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\".</li> </ul> </li> </ol> </li> <li>No context stored at DB</li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-4-create-a-security-context-for-an-api-invoker-with-invoker-entity-role-and-invalid-apiinvokerid","title":"Test Case 4: Create a security context for an API invoker with Invoker entity role and invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-4</p> <p>Description:</p> <p>This test case will check that an Invoker cannot create a Security context with valid apiInvokerId.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID), but user that create Security Context with invalid apiInvokerId</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Create Security Context using Provider certificate</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>body service security body</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Create security context using Provider certificate:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> <li> <p>No context stored at DB</p> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-5-retrieve-the-security-context-of-an-api-invoker","title":"Test Case 5: Retrieve the Security Context of an API Invoker","text":"<p>Test ID:: capif_security_api-5</p> <p>Description:</p> <p>This test case will check that an provider can retrieve the Security context of an API Invoker</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Retrieve Security Context by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-6-retrieve-the-security-context-of-an-api-invoker-with-invalid-apiinvokerid","title":"Test Case 6: Retrieve the Security Context of an API Invoker with invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-6</p> <p>Description:</p> <p>This test case will check that an provider can retrieve the Security context of an API Invoker</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Retrieve Security Context by Provider of invalid invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Retrieve Security Context of invalid Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-7-retrieve-the-security-context-of-an-api-invoker-with-invalid-apfid","title":"Test Case 7: Retrieve the Security Context of an API Invoker with invalid apfId","text":"<p>Test ID:: capif_security_api-7</p> <p>Description:</p> <p>This test case will check that an Provider cannot retrieve the Security context of an API Invoker without valid apfId</p> <p>Pre-Conditions:</p> <ul> <li>API Exposure Function is not pre-authorised (has invalid apfId)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Store signed Certificate</li> <li>Create Security Context</li> <li>Retrieve Security Context as Provider.</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Retrieve Security Context as Invoker role:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Create security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be aef\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-8-delete-the-security-context-of-an-api-invoker","title":"Test Case 8: Delete the Security Context of an API Invoker","text":"<p>Test ID:: capif_security_api-8</p> <p>Description:</p> <p>This test case will check that an Provider can delete a Security context</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Delete Security Context by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker but using Provider certificate.</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> <li> <p>Delete Security Context of Invoker by Provider:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Use AEF Certificate</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Delete security context:</p> <ol> <li>204 No Content response.</li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Security context not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-9-delete-the-security-context-of-an-api-invoker-with-invoker-entity-role","title":"Test Case 9: Delete the Security Context of an API Invoker with Invoker entity role","text":"<p>Test ID:: capif_security_api-9</p> <p>Description:</p> <p>This test case will check that an Invoker cannot delete a Security context</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority) and API Invoker has created a valid Security Context</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Create Security Context using Provider certificate</li> <li>Delete Security Context by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Delete Security Context of Invoker:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be aef\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-10-delete-the-security-context-of-an-api-invoker-with-invoker-entity-role-and-invalid-apiinvokerid","title":"Test Case 10: Delete the Security Context of an API Invoker with Invoker entity role and invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-10</p> <p>Description:</p> <p>This test case will check that an Invoker cannot delete a Security context with invalid </p> <p>Pre-Conditions:</p> <ul> <li>Invoker is pre-authorised.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Delete Security Context by invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Invoker Onboarding</p> </li> <li> <p>Delete Security Context of Invoker:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>Use Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Delete security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be aef\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-11-delete-the-security-context-of-an-api-invoker-with-invalid-apiinvokerid","title":"Test Case 11: Delete the Security Context of an API Invoker with invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-11</p> <p>Description:</p> <p>This test case will check that an Provider cannot delete a Security context of invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>Provider is pre-authorised (has valid apfId from CAPIF Authority).</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Delete Security Context by provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Delete Security Context of Invoker by Provider:</p> <ul> <li>Send DELETE https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}</li> <li>Use AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-12-update-the-security-context-of-an-api-invoker","title":"Test Case 12: Update the Security Context of an API Invoker","text":"<p>Test ID:: capif_security_api-12</p> <p>Description:</p> <p>This test case will check that an API Invoker can update a Security context</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context By Invoker</li> <li>Update Security Context By Invoker</li> <li>Retrieve Security Context By Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Update Security Context of Invoker:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/update</li> <li>body service security body but with notification destination modified to http://robot.testing2</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Update security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.</li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.<ol> <li>Check is this returned object match with modified one.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-13-update-the-security-context-of-an-api-invoker-with-provider-entity-role","title":"Test Case 13: Update the Security Context of an API Invoker with Provider entity role","text":"<p>Test ID:: capif_security_api-13</p> <p>Description:</p> <p>This test case will check that an Provider cannot update a Security context</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized.</li> <li>Invoker has created the Security Context previously.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context</li> <li>Update Security Context as Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Update Security Context of Invoker by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/update</li> <li>body service security body but with notification destination modified to http://robot.testing2</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Update security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\". </li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-14-update-the-security-context-of-an-api-invoker-with-aef-entity-role-and-invalid-apiinvokerid","title":"Test Case 14: Update the Security Context of an API Invoker with AEF entity role and invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-14</p> <p>Description:</p> <p>This test case will check that an Provider cannot update a Security context of invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized.</li> <li>Invoker has created the Security Context previously.</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF</li> <li>Update Security Context as Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration</p> </li> <li> <p>Update Security Context of Invoker by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}/update</li> <li>body service security body</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Update security context:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be invoker\". </li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-15-update-the-security-context-of-an-api-invoker-with-invalid-apiinvokerid","title":"Test Case 15: Update the Security Context of an API Invoker with invalid apiInvokerID","text":"<p>Test ID:: capif_security_api-15</p> <p>Description:</p> <p>This test case will check that an API Invoker cannot update a Security context not valid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Update Security Context</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Update Security Context of Invoker:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}/update</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Retrieve security context:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-16-revoke-the-authorization-of-the-api-invoker-for-apis","title":"Test Case 16: Revoke the authorization of the API invoker for APIs.","text":"<p>Test ID:: capif_security_api-16</p> <p>Description:</p> <p>This test case will check that a Provider can revoke the authorization for APIs</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context by Invoker</li> <li>Revoke Security Context by Provider</li> <li>Retrieve Security Context by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context By Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Revoke Authorization by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/delete</li> <li>body security notification body</li> <li>Using AEF Certificate.</li> </ul> </li> <li> <p>Retrieve Security Context by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Revoke Authorization:</p> <ol> <li>204 No Content response.</li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Security context not found\".</li> <li>cause with message \"API Invoker has no security context\".</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-17-revoke-the-authorization-of-the-api-invoker-for-apis-without-valid-apfid","title":"Test Case 17: Revoke the authorization of the API invoker for APIs without valid apfID.","text":"<p>Test ID:: capif_security_api-17</p> <p>Description:</p> <p>This test case will check that an Invoker can't revoke the authorization for APIs</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context</li> <li>Revoke Security Context by invoker</li> <li>Retrieve Security Context</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Revoke Authorization by invoker:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}/delete</li> <li>body security notification body</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>Using Provider Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Revoke Security Context by invoker:</p> <ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 401</li> <li>title with message \"Unauthorized\"</li> <li>detail with message \"Role not authorized for this API route\".</li> <li>cause with message \"User role must be provider\". </li> </ul> </li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.<ol> <li>Check is this returned object match with created one.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-18-revoke-the-authorization-of-the-api-invoker-for-apis-with-invalid-apiinvokerid","title":"Test Case 18: Revoke the authorization of the API invoker for APIs with invalid apiInvokerId.","text":"<p>Test ID:: capif_security_api-18</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot revoke the authorization for APIs for invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register and onboard Invoker at CCF</li> <li>Register Provider at CCF</li> <li>Create Security Context</li> <li>Revoke Security Context by Provider</li> <li>Retrieve Security Context</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Create Security Context for this Invoker:</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> </ul> </li> <li> <p>Revoke Authorization by Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/trustedInvokers/{API_INVOKER_NOT_VALID}/delete</li> <li>body security notification body</li> <li>Using AEF Certificate.</li> </ul> </li> <li> <p>Retrieve Security Context of Invoker by Provider:</p> <ul> <li>Send GET https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}?authenticationInfo=true&authorizationInfo=true</li> <li>This request will ask with parameter to retrieve authenticationInfo and authorizationInfo</li> <li>Using AEF Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li> <p>Revoke Security Context by invoker:</p> <ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails data structure, with:<ul> <li>status 404</li> <li>title with message \"Not Found\"</li> <li>detail with message \"Invoker not found\".</li> <li>cause with message \"API Invoker not exists or invalid ID\".</li> </ul> </li> </ol> </li> <li> <p>Retrieve security context:</p> <ol> <li>200 OK response.</li> <li>body returned must accomplish ServiceSecurity data structure.<ol> <li>Check is this return one object that match with created one.</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-19-retrieve-access-token","title":"Test Case 19: Retrieve access token","text":"<p>Test ID:: capif_security_api-19</p> <p>Description:</p> <p>This test case will check that an API Invoker can retrieve a security access token OAuth 2.0.</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerId)</li> <li>Service API of Provider is published</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token:</li> <li>body access token req body and example example</li> <li>securityId is apiInvokerId.</li> <li>grant_type=client_credentials.</li> <li>Create Scope properly for request: 3gpp#{aef_id}:{api_name}</li> <li>Using Invoker Certificate.</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>200 OK</li> <li>body must follow AccessTokenRsp with:<ol> <li>access_token present</li> <li>token_type=Bearer</li> </ol> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-20-retrieve-access-token-by-provider","title":"Test Case 20: Retrieve access token by Provider","text":"<p>Test ID:: capif_security_api-20</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot revoke the authorization for APIs for invalid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerID from CAPIF Authority) and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by provider:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token:</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error unauthorized_client</li> <li>error_description=Role not authorized for this API route</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-21-retrieve-access-token-by-provider-with-invalid-apiinvokerid","title":"Test Case 21: Retrieve access token by Provider with invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-21</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token without valid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Provider</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by provider:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{API_INVOKER_NOT_VALID}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>Using AEF Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>401 Unauthorized response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error unauthorized_client</li> <li>error_description=Role not authorized for this API route</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-22-retrieve-access-token-with-invalid-apiinvokerid","title":"Test Case 22: Retrieve access token with invalid apiInvokerId","text":"<p>Test ID:: capif_security_api-22</p> <p>Description:</p> <p>This test case will check that an API Invoker can't retrieve a security access token without valid apiInvokerId</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised (has valid apiInvokerId)</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li>Perform Provider Registration and Invoker Onboarding</li> <li>Publish Service API at CCF:<ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li>Request Discover Published APIs not filtered:<ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li>Create Security Context for this Invoker<ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li>Request Access Token by invoker:<ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{API_INVOKER_NOT_VALID}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>404 Not Found response.</li> <li>body returned must accomplish ProblemDetails29571 data structure, with:<ul> <li>status 404</li> <li>title Not Found</li> <li>detail Security context not found</li> <li>cause API Invoker has no security context</li> </ul> </li> </ol> </li> </ol> <p>NOTE: ProblemDetails29571 is the definition present for this request at swagger of ProblemDetails, and this is different from definition of ProblemDetails across other CAPIF Services</p>"},{"location":"testing/testplan/api_security_service/#test-case-23-retrieve-access-token-with-invalid-client_id","title":"Test Case 23: Retrieve access token with invalid client_id","text":"<p>Test ID:: capif_security_api-23</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token without valid client_id at body</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>client_id is not-valid </li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_client</li> <li>error_description=Client Id not found</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-24-retrieve-access-token-with-unsupported-grant_type","title":"Test Case 24: Retrieve access token with unsupported grant_type","text":"<p>Test ID:: capif_security_api-24</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with unsupported grant_type</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=not_valid</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error unsupported_grant_type</li> <li>error_description=Invalid value for <code>grant_type</code> \\(${grant_type}\\), must be one of \\['client_credentials'\\] - 'grant_type'</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-25-retrieve-access-token-with-invalid-scope","title":"Test Case 25: Retrieve access token with invalid scope","text":"<p>Test ID:: capif_security_api-25</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with complete invalid scope</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>scope=not-valid-scope</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_scope</li> <li>error_description=The first characters must be '3gpp'</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-26-retrieve-access-token-with-invalid-aefid-at-scope","title":"Test Case 26: Retrieve access token with invalid aefid at scope","text":"<p>Test ID:: capif_security_api-26</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with invalid aefId at scope</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>scope=3gpp#1234:*service_1*</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_scope</li> <li>error_description=One of aef_id not belongs of your security context</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/api_security_service/#test-case-27-retrieve-access-token-with-invalid-apiname-at-scope","title":"Test Case 27: Retrieve access token with invalid apiName at scope","text":"<p>Test ID:: capif_security_api-27</p> <p>Description:</p> <p>This test case will check that an API Exposure Function cannot retrieve a security access token with invalid apiName at scope</p> <p>Pre-Conditions:</p> <ul> <li>API Invoker is pre-authorised and Provider is also authorized</li> </ul> <p>Execution Steps:</p> <ol> <li>Register Provider at CCF, store certificates and Publish Service API service_1 at CCF</li> <li>Register and onboard Invoker at CCF</li> <li>Discover Service APIs by Invoker.</li> <li>Create Security Context According to Service APIs discovered.</li> <li>Request Access Token by Invoker</li> </ol> <p>Information of Test:</p> <ol> <li> <p>Perform Provider Registration and Invoker Onboarding</p> </li> <li> <p>Publish Service API at CCF:</p> <ul> <li>Send POST to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis</li> <li>body [service api description] with apiName service_1</li> <li>Use APF Certificate</li> </ul> </li> <li> <p>Request Discover Published APIs not filtered:</p> <ul> <li>Send GET to ccf_discover_url https://{CAPIF_HOSTNAME}/service-apis/v1/allServiceAPIs?api-invoker-id={apiInvokerId}</li> <li>Param api-invoker-id is mandatory</li> <li>Using Invoker Certificate</li> </ul> </li> <li> <p>Create Security Context for this Invoker</p> <ul> <li>Send PUT https://{CAPIF_HOSTNAME}/trustedInvokers/{apiInvokerId}</li> <li>body service security body</li> <li>Using Invoker Certificate.</li> <li>Create Security Information Body with one securityInfo for each aef present at each serviceAPIDescription present at Discover.</li> </ul> </li> <li> <p>Request Access Token by invoker:</p> <ul> <li>Sent POST https://{CAPIF_HOSTNAME}/securities/{securityId}/token.</li> <li>body access token req body</li> <li>securityId is apiInvokerId</li> <li>grant_type=client_credentials</li> <li>scope=3gpp#{aef_id}:not-valid</li> <li>Using Invoker Certificate</li> </ul> </li> </ol> <p>Expected Result:</p> <ol> <li>Response to Request of Access Token:<ol> <li>400 Bad Request response.</li> <li>body returned must accomplish AccessTokenErr data structure, with:<ul> <li>error invalid_scope</li> <li>error_description=One of the api names does not exist or is not associated with the aef id provided</li> </ul> </li> </ol> </li> </ol>"},{"location":"testing/testplan/common_operations/","title":"Common Operations","text":""},{"location":"testing/testplan/common_operations/#register-new-user","title":"Register new user","text":"<p>In order to use OpenCAPIF we must add a new user. This new user can onboard/register any Invokers or Providers.</p> <p>That new user must be created by administrator of Register Service and with the credentials shared by administrator, the new user can get the access_token by requesting it to Register service.</p> <p>The steps to register a new user at Register Service are:</p>"},{"location":"testing/testplan/common_operations/#admin-create-user","title":"Admin create User","text":"<p>1) Login as Admin to get access_token:</p> <ul> <li>Send POST to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/login<ul> <li>Include basic Auth Header with Admin credentials</li> </ul> </li> <li>Get access_token and refresh_token from response</li> </ul> <p></p> <p>2) Create User:</p> <ul> <li>Send POST to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/createUser<ul> <li>Include Admin access_token in Authorization Bearer Header</li> <li>Body user_registration_body</li> </ul> </li> </ul> <p></p>"},{"location":"testing/testplan/common_operations/#user-retrieve-access-token-and-other-information","title":"User Retrieve access token and other information","text":"<p>1) Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth<ul> <li>Include basic Auth Header with User credentials</li> </ul> </li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> <p></p>"},{"location":"testing/testplan/common_operations/#onboard-an-invoker","title":"Onboard an Invoker","text":""},{"location":"testing/testplan/common_operations/#steps-to-perform-operation","title":"Steps to perform operation","text":"<p>Preconditions: The administrator must have previously registered the User.</p> <ol> <li>Create public and private key at invoker</li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Onboard Invoker: </p> <ul> <li>Send POST to https://{CAPIF_HOSTNAME}/api-invoker-management/v1/onboardedInvokers</li> <li>Reference Request Body: invoker onboarding body</li> <li>\"onboardingInformation\"->\"apiInvokerPublicKey\": must contain public key generated by Invoker.</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> </ul> </li> </ol>"},{"location":"testing/testplan/common_operations/#checks-to-ensure-onboarding","title":"Checks to ensure onboarding","text":"<ol> <li> <p>Response to Get Auth:</p> <ol> <li>200 OK</li> <li>access_token returned.</li> </ol> </li> <li> <p>Response to Onboard request must accomplish:</p> <ol> <li>201 Created</li> <li>Response Body must follow APIInvokerEnrolmentDetails data structure with:<ul> <li>apiInvokerId</li> <li>onboardingInformation->apiInvokerCertificate must contain the public key signed.</li> </ul> </li> <li>Response Header Location must be received with URI to new resource created, following this structure: {apiRoot}/api-invoker-management/{apiVersion}/onboardedInvokers/{onboardingId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/common_operations/#example-flow","title":"Example Flow","text":""},{"location":"testing/testplan/common_operations/#register-a-provider","title":"Register a Provider","text":""},{"location":"testing/testplan/common_operations/#steps-to-perform-operation_1","title":"Steps to Perform operation","text":"<ol> <li>Create public and private key at provider for provider itself and each function (apf, aef and amf)</li> <li> <p>Retrieve access_token by User:</p> <ul> <li>Send GET to https://${CAPIF_REGISTER}:${CAPIF_REGISTER_PORT}/getauth</li> <li>Include basic Auth Header with Admin user/password</li> <li>Retrieve access_token and the urls needed for next requests from response body user_getauth_response_body_example</li> </ul> </li> <li> <p>Register Provider:</p> <ul> <li>Send POST https://{CAPIF_HOSTNAME}/api-provider-management/v1/registrations</li> <li>body provider request body</li> <li>Send in Authorization Header the Bearer access_token obtained previously (Authorization:Bearer ${access_token})</li> <li>Store each cert in a file with according name.</li> </ul> </li> </ol>"},{"location":"testing/testplan/common_operations/#checks-to-ensure-provider-registration","title":"Checks to ensure provider registration","text":"<ol> <li> <p>Response to Register:</p> <ol> <li>201 Created</li> </ol> </li> <li> <p>Response to Get Auth:</p> <ol> <li>200 OK</li> <li>access_token returned.</li> </ol> </li> <li> <p>Register Provider at Provider Management:</p> <ol> <li>201 Created response.</li> <li>body returned must accomplish APIProviderEnrolmentDetails data structure.</li> <li>For each apiProvFuncs, we must check:<ol> <li>apiProvFuncId is set</li> <li>apiProvCert under regInfo is set properly</li> </ol> </li> <li>Location Header must contain the new resource URL {apiRoot}/api-provider-management/v1/registrations/{registrationId}</li> </ol> </li> </ol>"},{"location":"testing/testplan/common_operations/#example-flow_1","title":"Example Flow","text":""}]} \ No newline at end of file -- GitLab