From b61c8186f274db1de27ae74263e699f00db37fc1 Mon Sep 17 00:00:00 2001 From: Katharina Sick Date: Tue, 6 Aug 2024 12:54:49 +0200 Subject: [PATCH] feat: init --- .github/workflows/build.yml | 39 ++++ .github/workflows/release.yml | 59 ++++++ .gitignore | 17 ++ Dockerfile | 11 ++ README.md | 87 +++++++++ examples/balancer/balancer.png | Bin 0 -> 11026 bytes examples/balancer/start.sh | 31 +++ examples/balancer/trigger_incident.sh | 43 ++++ examples/example1/example1.png | Bin 0 -> 47778 bytes examples/example1/start.sh | 59 ++++++ examples/example1/trigger_crash_incident.sh | 11 ++ .../example1/trigger_resource_incident.sh | 12 ++ examples/example2/incident.sh | 13 ++ examples/example2/start.sh | 76 ++++++++ examples/paymentService/crash_incident.sh | 11 ++ examples/paymentService/errors_incident.sh | 12 ++ examples/paymentService/ressource_incident.sh | 12 ++ examples/paymentService/start.sh | 31 +++ examples/proxy/start.sh | 44 +++++ go.mod | 3 + service.go | 184 ++++++++++++++++++ 21 files changed, 755 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 examples/balancer/balancer.png create mode 100644 examples/balancer/start.sh create mode 100644 examples/balancer/trigger_incident.sh create mode 100644 examples/example1/example1.png create mode 100644 examples/example1/start.sh create mode 100755 examples/example1/trigger_crash_incident.sh create mode 100755 examples/example1/trigger_resource_incident.sh create mode 100644 examples/example2/incident.sh create mode 100644 examples/example2/start.sh create mode 100644 examples/paymentService/crash_incident.sh create mode 100644 examples/paymentService/errors_incident.sh create mode 100644 examples/paymentService/ressource_incident.sh create mode 100644 examples/paymentService/start.sh create mode 100644 examples/proxy/start.sh create mode 100644 go.mod create mode 100644 service.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bfc3bf1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +# Workflow for building the demo service + +name: Build Service + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the main branch + pull_request: + branches: [ main ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + + - name: Set up Go + id: go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + cache: false + + # Fetches all necessary go packages and builds the project + - name: Build + run: | + go build -o anomaly-simulation-service + + + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dd52789 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Create and publish an OCI image + +on: + push: + branches: + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Determine next version and push tag + id: semantic-release + uses: codfish/semantic-release-action@v3 + with: + tag-format: 'v${version}' + branches: | + [ 'main' ] + plugins: | + ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator', '@semantic-release/github'] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to the container registry (GitHub packages) + if: steps.semantic-release.outputs.new-release-published == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + if: steps.semantic-release.outputs.new-release-published == 'true' + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + # Set version tag based on semantic release output + type=semver,pattern=v{{version}},value=${{ steps.semantic-release.outputs.release-version }} + # Set latest tag + type=raw,value=latest + + - name: Build and push Docker image + if: steps.semantic-release.outputs.new-release-published == 'true' + id: push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53b7983 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +anomaly-simulation-service + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# IntelliJ Idea +.idea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a932bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# syntax=docker/dockerfile:1 +FROM golang:1.22 + +WORKDIR /app + +COPY go.mod *.go ./ +RUN CGO_ENABLED=0 GOOS=linux go build -o /anomaly-simulation-service + +EXPOSE 8080 + +CMD ["/anomaly-simulation-service"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2f80ee --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# Generic demo service + +Purpose of this service is to simulate specific anomaly situations, such as: + +- Slowdowns +- Failures +- Increase in resource consumption +- Process crashes +- Calls to one or more other services + +Therefore, this generic service can be used to build all kinds of variable demo +situations with either flat or deep call trees. + +The service listenes on following HTTP resource pathes: +- GET '/' normal page return +- POST '/config' service receives anomaly config as a HTTP POST message with a JSON config payload that is defined below. + +Thanks to [Wolfgang Beer](https://github.com/wolfgangB33r) for the initial implementation of this service. + +## Usage + +Start service by specifying a listening port: +`./service.exe 8090` +Start the service multiple times and let the services call each other +`./service.exe 8090` +`./service.exe 8091` + +## Dynamically reconfigure the service + +Push a http POST request to /config on your started service. + +## Config JSON body + +Count always represents the number of service requests that suffer from that anomaly, e.g.: a count of 5 means the next 5 service requests are affected. +A crash anomaly kills the service process with the given exit code. The resource anomaly allocates a matrix of 100x100 elements multiplied by the given severity. +Callees let you specify the callees this service calls with each service request. Specifying callees allows you to build dynamic multi-level service call trees. +In case the attribute 'Balanced' is set to 'true', the callees are equally iterated with each request. + +```json +{ + "ErrorConfig": { + "ResponseCode": 500, + "Count": 5 + }, + "SlowdownConfig": { + "SlowdownMillis": 500, + "Count": 1 + }, + "CrashConfig": { + "Code": 3 + }, + "ResourceConfig": { + "Severity": 5, + "Count": 2 + }, + "Callees": [ + { + "Adr": "http://www.example.com", + "Count": 10 + }, + { + "Adr": "http://www.orf.at", + "Count": 3 + }, + { + "Adr": "http://localhost:8090", + "Count": 3 + } + ], + "Balanced": true +} +``` + +## Example topology + +The `example1/start.sh` shell script copies the generic demo service into 6 individual services. Starts those 6 services on the same machine on 6 different ports and configures them to call them each other in the topology shown by Dynatrace below: + +![](examples/example1/example1.png) + +## Balanced example + +The `balancer/start.sh` shell script copies the generic demo service into 6 individual services. Starts those 6 services on the same machine on 6 different ports and configures them to form a balancer and worker pool topology, as it is shown by Dynatrace below: + +![](examples/balancer/balancer.png) + + + diff --git a/examples/balancer/balancer.png b/examples/balancer/balancer.png new file mode 100644 index 0000000000000000000000000000000000000000..3a838a894985cecb9ca000bb83189e8e136d8e3a GIT binary patch literal 11026 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 HZg>6%yTzEE literal 0 HcmV?d00001 diff --git a/examples/balancer/start.sh b/examples/balancer/start.sh new file mode 100644 index 0000000..5dd8917 --- /dev/null +++ b/examples/balancer/start.sh @@ -0,0 +1,31 @@ +pkill balancer +pkill worker + +sleep 5 + +cp ../../anomaly-simulation-service balancer +cp ../../anomaly-simulation-service worker + +./balancer 9000& +DT_IGNOREDYNAMICPORT=true DT_NODE_ID=1 ./worker 9100& +DT_IGNOREDYNAMICPORT=true DT_NODE_ID=2 ./worker 9200& +DT_IGNOREDYNAMICPORT=true DT_NODE_ID=3 ./worker 9300& +DT_IGNOREDYNAMICPORT=true DT_NODE_ID=4 ./worker 9400& +DT_IGNOREDYNAMICPORT=true DT_NODE_ID=5 ./worker 9500& + +sleep 5 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:9100", "Count" : 1 }, + { "Adr" : "http://localhost:9200", "Count" : 1 }, + { "Adr" : "http://localhost:9300", "Count" : 1 }, + { "Adr" : "http://localhost:9400", "Count" : 1 }, + { "Adr" : "http://localhost:9500", "Count" : 1 } + ], + "Balanced" : true +}' \ + 'http://localhost:9000/config' diff --git a/examples/balancer/trigger_incident.sh b/examples/balancer/trigger_incident.sh new file mode 100644 index 0000000..6c16d2e --- /dev/null +++ b/examples/balancer/trigger_incident.sh @@ -0,0 +1,43 @@ +# first, configure the balancer to only send to 2 workers instead of previously 5 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:9400", "Count" : 3 }, + { "Adr" : "http://localhost:9500", "Count" : 3 } + ], + "Balanced" : true +}' \ + 'http://localhost:9000/config' + +# then, slowdown the remaining 2 workers because of the load shift + +sleep 80 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "SlowdownConfig" : { + "SlowdownMillis" : 500, + "Count" : 3000 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:9400/config' + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "SlowdownConfig" : { + "SlowdownMillis" : 500, + "Count" : 3000 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:9500/config' diff --git a/examples/example1/example1.png b/examples/example1/example1.png new file mode 100644 index 0000000000000000000000000000000000000000..e84e013fdc4c466c038ff9b43e9aa314b365ba0c GIT binary patch literal 47778 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

HQk^2P*OJE>oQ-tv5@GpH9l7}?UK0nkFAg< z`KKdY_U%^L3o}2so9qiFPfW#o$cjdloQtC6nBM-il@KQ3l9J&k)BjD0qCA|M6dAVi zeHp9=BxE*!u?eDH#O;oD7${lil%%z76a<2T@#1Ks&4HpRVY`X;V4;Cc$kvLu2T}Z4 zU!H7R$Ec3wVwGf1QSylN7ATxEppK`BQkXgH!`_vzaKUW+2>RZViBXR<&56}W>gdqy z)cKGiQC3a--m?vtpFEPLSWXEtMtu=ymhb)zmf#mpbeaxQT#FF zbB-T93f?QnTWkP>@B)wn9=2;}ZGi_0fIyMe$T{bv$$p-+fcQ|%c>%q?SBGp>muaH%o7UPD%!9waO$AV_o%k2F7_p|ZmFhPPypi28hhY*jQPNohbwTF|?WoPk>esn;C`4=k}IrSxdT%5P)BKb#h`+Oml zF1DStqy2v4R1aON+*Iy@>AF@}~^63ox9?fqSfTtNxee5w zI@#M}sOaeEl|hAqB_uKw9`mbL3dx@Ixq12N8ae5PmIEmB{KZftY**0$)dqe< zF2PR-FpbZq9o$Y^KZ4J%-#91U;pt0*b{zC1hioXMF%_0MeDSg;aNkX#qi!S#Zq#9c z3Qu`|2B#q(PK(lEe?a2^T9W{}jU;hC0IELg)x*8Ll%mAe#%HmTE8(S4&I8~=P4l-2 z0H^fLrU&XPA4Kmg6pcsG7uVP`w*9EYFOviZU9kbt(t)_yW;05{)wyqd85|pUg=oLq zhPm)}{6oj7UteZdr;*ZJ3#g{u{=oOKv{Z7FBE1m`=F$WQ70q^Vm=}VW=%Gn$9bYb_5R>DsGq1#IL}3g;>N$lYftt&S~7M z&K*227s(fkLUYKJy8vq5gQi#ej!QUBX5{s|RWl+e%-fW^+XeT_AgG2`i62zoOMDMa zn}gIH+2a=ov^L}gB-|T*5T#9nH?MYF*lO<2hgn?6a{3DC>N9GFOu?1n-BQPKg z7jH<)6A3bEfkwrlxBbCD$N6|W+q*IU7fFE|E*a4#uN^r_+ zhInvkJH69-Er1$2-u3*vd==nljr2(2Rb1$xA`b~#phXJhK0pjqJdZ$zK;}il<56Jt zzD-i^x856i@ZdoMP-(!wB!hPH$3W8F;r||uVPIMam1R4NppeJlo0~icTq(2ci2{w? z033}WWnPWTiEBYf_gKr_BqgLKu8Ib&2m2VttGi@P0bN|<3y`8jWJ10shNSSP;+(fP zE`R60S-p2O>W;btVWCskQkJ`fN};D7$WrSkTp>tXa!o>D8gYit!1-=F*|0L*6Tm2H zG_&VLF;vJsF(kC^Fq*w;y-<2OANs{~%xTdf#^dOo|WQc(ZR9Qp90$hKNUStm{5{^`HpLM8qhCP|xX5v+Ezw}miq^Ax5si0x1$u#K;L z%tsEo@J@Vw_LGqp3KG{MT`50Mg>l}+xf%DT%$B9PlI${E^o^_IJ=Lbe@>UXfeq35I z!<(7=^|pUE_Y^x@onRB=nW{OjEdPimQ;?p%@<@vv)1Jehma zHK>q+cqaBl_Zb|B-kKzOW|HV1m;#R~*D$NGyiiH9G>|UWqUz7V6Cz1--pSY4Th)GS z*VMB=Th`_7xhmAcye5s|J~VH(&~oPRr-d3_$p@-;*CrX#-(|L@(b)g(CxkBEx);&> zjyQRrl4p#esIBhOwX?ljv)Sl7ZzhVkPT`f%iF9vZy;X;_Poe9D%q4(z z^X7?72Zf%TS!Eq%$L2?wq+mUJF6FnFhM4lGOrFo1rf*eP+f^BVh&%=f-?#4$_Fbx% zU(cZ9v{_~7RaRX7vB_vdZ2ct?u)1on^(8BxwKMr*NlieUkEuEEUVlq8jv z97SK_#^NpQh4=z7;;@2C5~1Jgwe5DCjp5MDylRD zb1>wW?mn=I^qviXkKKdOvzq;W)v_YW`%;Op*{IBCEp^Lp6MSl04{q57k$*66uc!FQ z=}2$DoJ;h&ZGzGqq}3F=NUJ~g+`*Z7jGQ$&8sjmnR`JZ17d* z`v7fPSKf@M0NU(Nt{PNG!VAu<=>Rh(mk2SH-jlY?-(-?qLZ}~2v7Jl3`BC+ev#kmi`a*MvDhC|BMMxOxeVpd>qyP4M z!<2}3p54pj9AA8`!e*NEt}WgoP>kI+gQ7xmBBr}hS!e9)SNcp(2Y(07bKWn6)wyT( zQqo$?Mz8_h6DiuP=fKQc%nu*CZ%0iyT03jY$UoI8Ztq!gYqRfE^!sd!8Jp~gEnq!z(*_RI~w_)j{$hQKPwxh;t4JB}YekK}? zTGm*N1^DSCe=^D>DmX2jn^4xN+h*OsK73h5R8K`)97%?2?8riZwh`3hEsHWGn^{-T%Yp4 zO&>*E=^j$u*LY%k?+9A1!Aww3^GD~Q2D34?VJzK;0P&=^H5&EW9?{C(8ubmo51U6u z#WfYsB6OW@5?v?ZpvdBvm>+j^dvM%u;G?h4Hq=xEy-|ZaUW!UrW=|JLPwFLFx&>#k zLTM>|vNwTK-%5PmAnkPXhw}1b{{oe9(ev9Lb`~dJzL0x-vVSt4$E$SOhVe4LT5l1B zF?W^gWU0Mo|0N-d1HD~AYmWO~uI-1HO&UCEG&2@hZF9dMvvdDeME)AI$gUme{xj>n zp=J6m@){3b9l{-?>=iP)g@Spx&f&e6IQ#$Bb$qhT(5oxn_&biJ@tz?LUs9N$OE#mL z(WoyD(>2%qoqURmu&Da}PuB_VYrdZst&Pt1{(j$~bk8`Bnr14nS*kHdBkat!$S-~C zorPE9xZQUbP6^gRNhrMQ(@(uR{JA53r=tRHh0_V@THYvIuEgq3dP)W5E^8_uxE4yVuCJ7Z>S_;81jWK!{Rc~zcY`gJmNLO@dB z_xvkpmh-ANm=7y2TKM-#I&vi?B3J!QC`LQialXC8M&T2Vc@N1GZ zbCu|&79 z!P~+1am@`HHImW8HMghaiDt%LM7svvJ(dM!8f*#~8QC9+6T*`W4ZQa(dXMC1b<`cw z;uVj%&q*jc@1(=uj5asU8PC&Zk===2E5P|7qlAJwsO<|pCyWVl`Te}GEObNP&SA1o ze53q~<8lxqTVB;`+l|a?tCKTq#q=f{ngh~lBGSv==cCtsn7L#|hdWYoUYmNY-Sp~N zyT*TXX6j{!e|omG2CEn(IiwmrOl`R*)QyZ&JM6>gi&r){HG7py_Av5`Y&qZG#F+ll z@@xS*ga!I_a_5|^{_mXE-9-iCP7O|DF;!l+1};gVLNVjA*JI2koP%2jErvYHqx#C? zw|K5s)%`M8ot#4BqG-J`9~l)*X_Oc6nFUd#xatb$&3#m}*1B%3DJN+->xajr9;!JY z`L00doIuR|ikG3Q#;2aD4pN=piN&r!WEBTV3fSb})`gFKQn_?*tRNo@w(C`@Cn{sq z>*RBd*d31&XtL+uo9bivpK8;+#DkxrW)Z7Y0K1RSD`V zSu|hl&^zdwbLP%F-qZSZXn}u7%z#2&0q$Tww917Gn6*D`Z5F~ua$XDXujpn%Jf%VC z_Z2j$d-t_4=7sv`iDy(iBtM$c#E3Ao8u`gtrvBTuRC)NqUHPCp^W5u${QAx3b9FQ6 z5KkvGxIFwxwz9)*y4De)(v3KJ6CY^n(#qie01`Q{J}Y2bO>S=lm*50HiY*7Y+3w$0 zyIW%VWrt$&{33keCwDraB%Zpjpl0*tgXBeftN^WvTpM#lQ-8_rYAkQD@|ML|wuQyZ z&Uxqjdt=PUC%9VmSJPzVT>l!Zhb=TaLUl^b05sG14xz1HtvNv|59JBYeengrrV!`8 zM?wqb$iE9FSzMUCoAC`y5KJX=$72lVTyDY`EbR(sb;kbe0SSgbcVsK@k zBaM~~RHB2U12lX59bwXRRkEJ0W60w}oWZ44gzu>d@SxILC?B{7p66qRE+Pv) z6zJZrilb)_d=dWn-ew2AYCr$7+TXpQN&<_gz$Tit+_2M``1S28 zV^46EW;rx#Mn$q>F3`~9&M~M~97lWf?t#tHfx_6K;ky>Xdr7;$a01!=FaLe_cPKS> zwW$^QZO3Fa-si?H(rn>uy(m>SlR_YDSdZ}S3a(1{F&|7Um{LrKVU14(4VS%{qNL=hrfG-F|ndBX?({!a!v+EfcL$Vb}sjW)y&TEo!b0gnh z>?zBLx79P&fUv1=sU*b-~!ByD`z-~-c(a_qMQM9wIEgPC9^<5_-{R$VVbyN3`I%dF# z%>x6dt~~uB`-oxknh?l6;HO7FK&UBmjA@71=d<}c6dCguL;8Wr5cz4lp-LY>o@(;Q z@W4yK*h0W8YQL~8(ERQdVyJsUuBt%x^ zyaU#uA5<58h8hB({7GLY*31_`!=fHSP7v@hdFV>Xu1}Az93T_;AZF?dRHbo=D{f>q zPXUNn@464P)Dy@Pe#jF!(PB>R$JFBbaIQrufA%_c^z@{~q@Tm=kphh%%T4H^Av>W~ zI#ip`DQZ1b36glWMO~8tiBzQflm9(1hp&I2$W+)1UH6draR%r>F$=U^+)F*PlxV4ZTIh|VVw%O-YDqD zI0>H<0Gfv7CH-|-hgBfoHXORX0xyh2ra>B_IWZoYUdSmNaM^UA_yNE|s58s0-CjmR z9pae_SpXG5o3xFY((Peoi7Rbnzex;S;P@uLhDCpeiG@M)Kxpb`3N7ijH%hl%Ce8Eu z#GkYx?Ge$@1-D})wt$4-vRblg)D(4LxJI6D4hU#h?sANyoo55mq3dFD^_I!Xm{Dxl zLd$ViMfaabS4OY+D43`YvewoXkik7z6(0bw{_4c5mi+*iHpVi$1$OaSjX*V|Y@UMh z`o;zp2v+*~`j9Yq&&1j?mW`!KcHb9i@l}q3wz2w@+AQ}9^NjV%mfMp>XxCO91zj=2Kk(h7K zsiZel#g%Pt8$KiaZC-IZ*rnI&@y{~(h?AD_yUMdvEvsd1E`okXZaB+mkRiEBv*GHs2{xOhu%C&scO~oNK;N=UV}C~ zd$zHc3`(==URoj{UHk4QCD*yKL(55OQ)Dk#5= zCBKa+e<)&=veMET`;*4~9nv*)LK=4PCKyw)`?u_n>9L&0v>d1X9q;lYh|h4M{ZJHM zR$&<$2>HAG40bCG*E=2VXgPd3%#?ZU#_dEO$~tJykbK+^O~%652i-vt9a3>2*gyjM znG0(@)M(AhGdWdmC7Tk41ayG=XPL6`g?+ix{?JP`=+-bH_pi6ZF`#H?!i@)xgrT=Z z?DHcyUWB~oQra%^v&&Z`@C|{&+a z(SYOV_)`1FF-MPlE4e(92E`w(JJ7W#^DByYxY3$j;;0{?+?IEBDfu-rkC zS|bL+>Q)CbuiYwsFW>(hgLZd}JJ(^1WW*#PW4zfNg9Cg8GWDG?reO@Pzgw#Vvy+qj zmKbn9fkz%Ij2Nwx$m$?NAOK(2LtzI!Y1l|0IFt>U97DAKJfi(H%1o3Y$It>uUp{Dm zW(5UnkuQ;{AnNxg_{drKyEi?~WEOB#kTKBSQYQ1RTTCz3Jh)BRdz1#*2X_z@%R~77 zjW0E?Gb3ftd-KzU*9_d&MxYz)WA;ExadB}lnQh2M>yhSU^FXGe-*r_#B;OH~qy%y{ zFAp7eTkg;rdx07L!X6v(uj!jmLq9q|a*F}06`zCVa!8&g>qYOv2NBrhNN zv!Tfx!dL=sZ6nfT4q9V0KZZRfqX8u$i!_89Hs}@*{jWl-Mr1EPH?@M^#s0tc&ODmR z?_J=h`Vo?%NX9fs88Z(V8jw=SkaBP&nKMQynKFcgQl_uU7>a{39CKvISmsbNCi57_ zc%S_qeeZYOb=O^Y-FyGM|5(f7-S6J}-FrXJeumGZCPXNotY^`&hV`?W0dxl} zKMzJ(ioFK&fWTmQQ4b*~8;AR|2(r-U{)~iK2ryPrMpp9l z$-n%Bw`{9&78rX+Bo6DpCgeUU*bM1LODxWh45W>ylgyWW-=kJ|P%A1}D+#{Bsl4vW z32QyM8U1vEY#j6gGYXmg#L>4Lhc7rgFQ2c`LlJ>=9RT|re3(C^vtvIvrPQjz}AieSE zO9jC)!d*?C=?G2fUDi(02$f?!vDlPpK3Qjtw3zJkNxsNp9-D7mD6Ashf z^eNaZ**fDVSHN$|IncaF?yz`JSlRe44zG>iNJHquq6G~-k#kKk54@Igw1~C9uJ_Sn zpTJxE!Bf%=-F4NzZ09R><&e&bYiRgCQ15deDPd^TKsK10Z{j*W$x(eS$@N3cy(LU5G$kMWhOPcmG+5)N1(*b%V520(GO046xg5T z+dHK^WKp#vP~gy9j!SRgLVo3x@^8pGfy69Wci3X^St<+PCrzU}Dfl#JF&-FaM;RZc zt#4I3aWj~cEJo`m97|qIQT5)X(gC0;D37PHv@7X(pX-1?3*jeJ2jK_2l^Vy#ZTQLlymQ)4>>J>FBGw;qwKeLk11>^DUjS z@ErSc8U*J-8xe<{1IP^nis5?(*wK&d&|zerUkJ`r8aOUw(FoAqL>^GI@6C_EbF95s zbNYY`86q|+EdCVB7a+_RJ=*fV^6#W4J*0iANMp}Jf&gSYoe4iQIW|VE2YE%+R5I)q zE1QizVh4yLWAQ#)kOTT~hI=qRMhopcI9>`N^J(Vfg)``dnb-?Y!wrxfwuUe)NTY*D z16JCV=q)UzPD-wAT5w*#A;!nR87VXnZEPTM3Ggytegov@??>Kkh>RStA$Tw%iG~BG z8K&-b`l3z?V4Vi{8#ds>=>3t{`{4$PiIN!97^UyXafW1=B*=V^P=BoNDBGzc*OuV! zUV^e|JCW>A;l&!Q`0?XMWe__g8C!qfLV6D{WOVI8aG{&#D^D)Y_^oW+=YH1FQ7u<^ zoWhAej)8orM%vrk4@-1GC>jAbgHWt+9m}~7HWC2gxP6L-%n<9})|+O`%6+%z*IUxU z&35$DhXWK8&=+@?!HXF(S3G4ofd&d!jcAXR&~y5L98EkIK9_kGzWuHs!c0G8GdTE% zMJvuK+AB4nNCt2w1H*PGL{K#UzQr+g5eq$4&7rQ;P|Zys?-D?%KHG3NAcleRJg`j0 zCnu$HJ(YxFAVUke3;*%xF+awTbCet0DGD;)T#T(`;3M!|@6Qcia@;{P&0 z8}1CQ6m|i!^hv(+$UY*tp{5{8y^;_l*^DDemq?3@Ha93j4?|a1Q?ZpGN)=tKcyHjV zuJE>v^kbyrqGi~G;Pl?2K470{Vxz1Mj8i{A6-4MnFnf(8bf4BkUQ^g(2#)N*5j%a3 z9o?jAb|VV}BnYDwgpDBNfVi7Qyk1#2P=`>xIB= zK~hluZW68xM>QvaLj!R+1$UF1@nzr9Q7p>Hu( zbTFGol=|07YR?3a_mBnZ?G9MBK{!$g5SScJ-~r~MUjuac7b}GIL$-2qa)W7)4(O8X z+X*G6^-Ap*#4xvR%*FqCKA@SRG!EsA%4Z_x}o zkN>iu9UbHsRP1(p}aZi6OE`w@XnzeJgv@|meIW4fT!p}N>e4vN1Q>s z>IsTO8vbsGs_+pn{v~}3t|UL#mfE+vFBE`nhg0^}Fev)mko!|ZGjlSSyDwYE9$%10 zT$L2~V%|YYN^~6{yXGuF@b+mqim~m^^<^<}9t^X7Ty9rJxHQutrz+vhpb23AH4O?^ zAIr_z8JOfn?u*eiV6@O-u;nmtQX}*2wBUR&bCiHS=2gHkRq8ti6??7z{@pb=e_hOs z3%UUm?f1Hp2Db_#Qd{P03JPVy&lKSuf?w?Dtp&6_bb(0BX#zG4!bH7h&sECX0KMyg<7roBtN|3KK?K;B6w} zqI%VCS8*742JbgpNSXxy^$JlkY-&uvLMDw~C^Q@DyxJ3KvFk5|8OVDLcj60zS-n3nGyd;(Y+qB>xx9BjOJ zoM*kJRbZySS*LuDloVq_SD0BKAnv#9DGO%=)c|Xo3~gUNOG}t=|FJ2TX@BdJ?9sOS zb6-?LHIj0sD$-6$ua8DIy|JlL6%90B591?gBA45f6u_fZ=@P~bt5^iCOhM#zt=;Wc zTplxX{NKc3ZB&`n zKiM-ebD zXS_Jxk8LxEfq&kP(^8k%i8qQdlqqGgh}Dv6c0ds~9H(YB(mhHba~|qmkx3AHKOZ$)i3zfCYxJQu|6J~|t$6M8;vp?0L79++{|Vzi1U4OLO_9ig z7$)rsxArGg|M**gccpOoZLrVI9tGT*kH!RQ*!LIY0gC`L97kMH?yo*a*F1r)x#F)6*CJ@IQ^9F5V8PTcYX?cZNr! zDb|1uVWA-+?zWc~Yygs8UyJWAgQUQ!>cFl35Cbx?)B9~Fh;x4UkEL~Lu+ln8(tmBG$)=58v_Ie(x;ET<{z1xuHudkjJgic zB9Iv`{*l7{19IG=wr#?NftyU@IOHtgvn^XK#ZBOPpQO@vCCB=zu5LTT=iS~y&H?5m zj0xc9OUA2i80{m0(5lA@OT+O%iK(_D zjrU&@8X)}QmMgP6%_TQ7F##l~EVr?0fX6&rp&iORfExs3k&y{R{j=K^PJ;QZj3+Tmu;sa%CS`F>t=~lg4kv&dCH5J&*ir% zgCu?}2pfVjq_dWbSvj+GX?@gsBkNHlr^`|wRmAs*aDQEPOWCrhHK>_3?^&m9;0dKm z`@p1Ml(P4CJx@kuix^Gvbe|rxEpI9HN#%-H$np!7fP*bb$+$*;=)U0 zJzWgEJbn80UULV!r<=Z}Xj3Qj6j<@kN^wU6FbThECa>Dgn?cF6<%T$k>m2`$!~MikjNvhtE|SL081)GPu{$u> z@rGpJ<(DJ0^EA#1rvA*!sScb0e-1g1ClDOAkoWy&?){sS{_>jJGlh&!ip{lm*VXSS+}=pY!-g60o@FmNH?gc(>L?!O-2UG~ zayIfnpU<7KG?8F`$4dIL!+V5v8ox*H)H~^t)c8Ku+LV{xmrvREM71~6bls>(_sDpv zIux&BH*{SwC^{G~FGh?3^73oI%BfCp&A6G7ENNLAYs6%Z#BIx%`r{g+$J=JKGeb?@z$ zk2-VGflX(lyckEZv{c`h8a2BEFOFY$sD}5#o7_7*PCJ5Ypf6V!dS$azKb~DgX5h`# z@q+EyW#8NL@%7uA`u9sCuW4)-d6VFq7igC}Z=chWZkW+{OXO-DpzF;&Yh_(2=pC#$Sp?lO2^ z#*gDCFX-tg0%>U8_A}tpyX*_f8te{aiLT`&JLP1@82PTHl@w-O^3nB~>R|JVg)&rI zY}J=@9Be*h8S5%_I88wu&DiTT*;{qPb8>C|FN42L84a!mdkNNXqiSKWGSg{?7Iniz zKWE3q{Z4NnEV#bqsFJ@nzZfMY;vk_Bo&=ouUBZk*h0p!HdhaS#%vTwxIUc%YW-ffh zva8Ad1+b1PYKPNnpHNuHYQXMPa%3b zQh9M2n&sguwCy)V96%2<1Z$qNH;EU`DYZ; z0R}zX0Z?mE4M1e7Lvimju>DJ`LHWi(zGzcd!Q{PHoOL{`;FP`#wt^R`Ndlu z)GT=&Tp1V`qWYPWBRM4h9TmP9rubp#4wVO#HQVG-3Mg)B4}DoM{m7_$`u>d*pIk5D zluO!#!);<6B1jy+(s4>}JH$n7Ubz9z8XV5M4$6q*JVMA&fgQI~6zrim+{vMB@KdSU x6`sP;M51!YIG$N3h>hE4^8XM0PbMNVk}5`$XUc9iUJ^~Fn(~Fy=_if6{teJ4Rqp@* literal 0 HcmV?d00001 diff --git a/examples/example1/start.sh b/examples/example1/start.sh new file mode 100644 index 0000000..6c1d7ac --- /dev/null +++ b/examples/example1/start.sh @@ -0,0 +1,59 @@ +pkill customerAPI +pkill sideService +pkill calcService +pkill restService +pkill recomService +pkill modelService + +sleep 5 + +cp ../../anomaly-simulation-service customerAPI +cp ../../anomaly-simulation-service sideService +cp ../../anomaly-simulation-service calcService +cp ../../anomaly-simulation-service restService +cp ../../anomaly-simulation-service recomService +cp ../../anomaly-simulation-service modelService + +./customerAPI 8080 > customerapi.log 2>&1 & +./sideService 8081 > sideservice.log 2>&1 & +./calcService 8082 > calcservice.log 2>&1 & +./restService 8083 > restservice.log 2>&1 & +./recomService 8084 > recomservice.log 2>&1 & +./modelService 8085 > modelservice.log 2>&1 & + +sleep 5 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:8081", "Count" : 2 }, + { "Adr" : "http://localhost:8082", "Count" : 3 }, + { "Adr" : "http://www.example.com", "Count" : 1 } + ] +}' \ + 'http://localhost:8080/config' + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:8083", "Count" : 2 }, + { "Adr" : "http://localhost:8084", "Count" : 2 }, + { "Adr" : "http://www.example.com", "Count" : 1 } + ] +}' \ + 'http://localhost:8081/config' + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:8085", "Count" : 2 }, + { "Adr" : "http://www.example.com", "Count" : 1 } + ] +}' \ + 'http://localhost:8084/config' diff --git a/examples/example1/trigger_crash_incident.sh b/examples/example1/trigger_crash_incident.sh new file mode 100755 index 0000000..1d98b94 --- /dev/null +++ b/examples/example1/trigger_crash_incident.sh @@ -0,0 +1,11 @@ +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "CrashConfig" : { + "Code" : 9 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:8002/config' diff --git a/examples/example1/trigger_resource_incident.sh b/examples/example1/trigger_resource_incident.sh new file mode 100755 index 0000000..a9804b2 --- /dev/null +++ b/examples/example1/trigger_resource_incident.sh @@ -0,0 +1,12 @@ +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "ResourceConfig" : { + "Severity" : 100, + "Count" : 30000 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:8006/config' diff --git a/examples/example2/incident.sh b/examples/example2/incident.sh new file mode 100644 index 0000000..78a0052 --- /dev/null +++ b/examples/example2/incident.sh @@ -0,0 +1,13 @@ +echo Triggering a slowdown of the database service by 50ms +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "SlowdownConfig" : { + "SlowdownMillis" : 150, + "Count" : 10000 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:9305/config' \ No newline at end of file diff --git a/examples/example2/start.sh b/examples/example2/start.sh new file mode 100644 index 0000000..0060729 --- /dev/null +++ b/examples/example2/start.sh @@ -0,0 +1,76 @@ +echo Killing all the running example processes +pkill stockweb +pkill stockticker +pkill loginservice +pkill contentprovider +pkill database +echo Wait 5 seconds before starting processes + +sleep 5 + +cp ../../anomaly-simulation-service stockweb +cp ../../anomaly-simulation-service stockticker +cp ../../anomaly-simulation-service loginservice +cp ../../anomaly-simulation-service contentprovider +cp ../../anomaly-simulation-service database +echo copied all demo processes + +./stockweb 9301 > stockweb.log 2>&1 & +./stockticker 9302 > stockticker.log 2>&1 & +./loginservice 9303 > loginservice.log 2>&1 & +./contentprovider 9304 > contentprovider.log 2>&1 & +./database 9305 > database.log 2>&1 & +echo started all demo services + +sleep 5 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:9303", "Count" : 1 }, + { "Adr" : "http://localhost:9302", "Count" : 2 }, + { "Adr" : "http://www.example.com", "Count" : 1 } + ] +}' \ + 'http://localhost:9301/config' + +echo configured stockweb to call login service and stockticker + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:9303", "Count" : 2 }, + { "Adr" : "http://localhost:9304", "Count" : 2 } + ] +}' \ + 'http://localhost:9302/config' + +echo configured stockticker to call authetication service and content service + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:9305", "Count" : 5 } + ] +}' \ + 'http://localhost:9303/config' + +echo configured login service to call the database + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:9305", "Count" : 5 } + ] +}' \ + 'http://localhost:9304/config' + +echo configured the content service to call the database \ No newline at end of file diff --git a/examples/paymentService/crash_incident.sh b/examples/paymentService/crash_incident.sh new file mode 100644 index 0000000..d796d82 --- /dev/null +++ b/examples/paymentService/crash_incident.sh @@ -0,0 +1,11 @@ +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "CrashConfig" : { + "Code" : 9 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:8083/config' diff --git a/examples/paymentService/errors_incident.sh b/examples/paymentService/errors_incident.sh new file mode 100644 index 0000000..ffd1c4e --- /dev/null +++ b/examples/paymentService/errors_incident.sh @@ -0,0 +1,12 @@ +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "ErrorConfig" : { + "ResponseCode" : 500, + "Count": 100 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:8083/config' \ No newline at end of file diff --git a/examples/paymentService/ressource_incident.sh b/examples/paymentService/ressource_incident.sh new file mode 100644 index 0000000..ffe00c4 --- /dev/null +++ b/examples/paymentService/ressource_incident.sh @@ -0,0 +1,12 @@ +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "ResourceConfig" : { + "Severity" : 100, + "Count" : 30000 + }, + "Callees" : [ + ] +}' \ + 'http://localhost:8083/config' diff --git a/examples/paymentService/start.sh b/examples/paymentService/start.sh new file mode 100644 index 0000000..8de5631 --- /dev/null +++ b/examples/paymentService/start.sh @@ -0,0 +1,31 @@ +pkill paymentService +pkill authenticationService +pkill persistanceService +pkill riskAssessmentService + +sleep 5 + +cp ../../anomaly-simulation-service paymentService +cp ../../anomaly-simulation-service authenticationService +cp ../../anomaly-simulation-service persistanceService +cp ../../anomaly-simulation-service riskAssessmentService + +./paymentService 8080 > payment.log 2>&1 & +./authenticationService 8081 > payment.log 2>&1 & +./persistanceService 8082 > payment.log 2>&1 & +./riskAssessmentService 8083 > payment.log 2>&1 & + +sleep 5 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:8081", "Count" : 1 }, + { "Adr" : "http://localhost:8082", "Count" : 5 }, + { "Adr" : "http://localhost:8083", "Count" : 1 } + ] +}' \ + 'http://localhost:8080/config' + diff --git a/examples/proxy/start.sh b/examples/proxy/start.sh new file mode 100644 index 0000000..eb21318 --- /dev/null +++ b/examples/proxy/start.sh @@ -0,0 +1,44 @@ +echo Killing all the running example processes +pkill caller +pkill proxy +pkill destination +echo Wait 5 seconds before starting processes + +sleep 5 + +cp ../../anomaly-simulation-service caller +cp ../../anomaly-simulation-service proxy +cp ../../anomaly-simulation-service destination +echo copied all demo processes + +./caller 8497 & +./proxy 8498 > proxy.log 2>&1 & +./destination 8499 & +echo started all demo services + +sleep 5 + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:8498", "Count" : 1 } + ] +}' \ + 'http://localhost:8497/config' + +echo configured caller to call the proxy + +curl -i -X POST \ + -H "Content-Type:application/json" \ + -d \ +'{ + "Callees" : [ + { "Adr" : "http://localhost:8499", "Count" : 1 } + ], + "Proxy" : true +}' \ + 'http://localhost:8498/config' + +echo configured the proxy to call the destination service diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..faec3db --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module anomaly-simulation-service + +go 1.22.5 diff --git a/service.go b/service.go new file mode 100644 index 0000000..d8304b3 --- /dev/null +++ b/service.go @@ -0,0 +1,184 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +type errorAnomalyConfig struct { + ResponseCode int + Count int +} + +type slowdownAnomalyConfig struct { + SlowdownMillis int + Count int +} + +type crashAnomalyConfig struct { + Code int +} + +type resourceAnomalyConfig struct { + Severity int + Count int +} + +type callee struct { + Adr string // URL address to call + Count int // number of calls per minute +} + +type config struct { + ErrorConfig errorAnomalyConfig + SlowdownConfig slowdownAnomalyConfig + CrashConfig crashAnomalyConfig + ResourceConfig resourceAnomalyConfig + Callees []callee + Balanced bool + Proxy bool +} + +var conf config +var reqcount int + +func receiveConfig(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + w.WriteHeader(http.StatusNoContent) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + //fmt.Printf(string(body)) + err = json.Unmarshal(body, &conf) + if err != nil { + fmt.Printf("config payload wrong") + log.Printf("%s config payload is wrong", os.Args[0]) + panic(err) + } + log.Printf("%s received a new service config deployment", os.Args[0]) + default: + fmt.Fprintf(w, "sorry, only POST method is supported.") + } + defer r.Body.Close() +} + +func handleIcon(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() +} + +func sayHello(w http.ResponseWriter, r *http.Request) { + var buf bytes.Buffer + reqcount++ + fmt.Fprintf(&buf, "it's the %d call\n", reqcount) + fmt.Fprintf(&buf, "what I did:\n") + // first call all callees we have in the config with the multiplicity given + failures := false + + for ci, element := range conf.Callees { + if !conf.Balanced || reqcount%len(conf.Callees) == ci { + for i := 0; i < element.Count; i++ { + req, err := http.NewRequest("GET", element.Adr, nil) + if err != nil { + // os.Args[0] to get the current exe name + log.Printf("%s error reading request.", os.Args[0]) + os.Exit(1) + } + if conf.Proxy { + log.Printf("%s dt header: %s ", os.Args[0], r.Header.Get("X-Dynatrace")) + log.Printf("%s RemoteAddr: %s ", os.Args[0], r.RemoteAddr) + req.Header.Set("X-Dynatrace", r.Header.Get("X-Dynatrace")) + req.Header.Set("x-forwarded-for", r.RemoteAddr) + req.Header.Set("forwarded", r.RemoteAddr) + } + req.Header.Set("Cache-Control", "no-cache") + + client := &http.Client{Timeout: time.Second * 10} + + resp, err := client.Do(req) + if err != nil { + log.Printf("%s error reading response.", os.Args[0]) + os.Exit(1) + } else { + if resp.StatusCode != 200 { + log.Printf("%s got a bad return", os.Args[0]) + failures = true + } + } + defer resp.Body.Close() + } + fmt.Fprintf(&buf, "called %s %d times\n", element.Adr, element.Count) + } + } + // then check if we should crash the process + if conf.CrashConfig.Code != 0 { + log.Printf("%s cashed.", os.Args[0]) + panic("a problem") + } + // then check if we should add a delay + if conf.SlowdownConfig.SlowdownMillis != 0 && conf.SlowdownConfig.Count > 0 { + time.Sleep(time.Duration(conf.SlowdownConfig.SlowdownMillis) * time.Millisecond) + conf.SlowdownConfig.Count = conf.SlowdownConfig.Count - 1 + fmt.Fprintf(&buf, "sleeped for %d millis\n", conf.SlowdownConfig.SlowdownMillis) + } + // then check if we should increase resource consumption + if conf.ResourceConfig.Severity != 0 && conf.ResourceConfig.Count > 0 { + for c := 0; c <= conf.ResourceConfig.Severity; c++ { + m1 := [100][100]int{} + for i := 0; i < 100; i++ { + for j := 0; j < 100; j++ { + m1[i][j] = rand.Int() + } + } + } + fmt.Fprintf(&buf, "allocated %d 100x100 matrices with random values\n", conf.ResourceConfig.Severity) + conf.ResourceConfig.Count = conf.ResourceConfig.Count - 1 + log.Printf("%s high resource consumption service call", os.Args[0]) + } + // then check if the should return an error response code + if failures || (conf.ErrorConfig.ResponseCode != 0 && conf.ErrorConfig.Count > 0) { + if conf.ErrorConfig.ResponseCode == 400 { + w.WriteHeader(http.StatusForbidden) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + conf.ErrorConfig.Count = conf.ErrorConfig.Count - 1 + } else { + message := r.URL.Path + message = strings.TrimPrefix(message, "/") + message = "finally returned " + message + w.Write([]byte(message)) + } + defer r.Body.Close() +} + +func main() { + port := 8080 + if len(os.Args) > 1 { + arg := os.Args[1] + fmt.Printf("Start demo service at port: %s\n", arg) + i1, err := strconv.Atoi(arg) + if err == nil { + port = i1 + } + } else { + fmt.Printf("Start demo service at default port: %d\n", port) + } + + http.HandleFunc("/", sayHello) + http.HandleFunc("/favicon.ico", handleIcon) + http.HandleFunc("/config", receiveConfig) + if err := http.ListenAndServe(":"+strconv.Itoa(port), nil); err != nil { + panic(err) + } +}