Compare commits
	
		
			442 Commits
		
	
	
		
			chart
			...
			443546027b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					443546027b | ||
| 
						 | 
					1c4f3f756f | ||
| 
						 | 
					3971d5ca5d | ||
| 
						 | 
					e95d1aa53b | ||
| 
						 | 
					1ab2bd2153 | ||
| 
						 | 
					d35ad440fa | ||
| 
						 | 
					d58dc56024 | ||
| 
						 | 
					34b7cdcf06 | ||
| 
						 | 
					af0d7807bc | ||
| 
						 | 
					e600fedcab | ||
| 
						 | 
					147c7bc3a1 | ||
| 
						 | 
					3fdf5c3ebf | ||
| 
						 | 
					cd177bd779 | ||
| 
						 | 
					2c8dcbe93d | ||
| 
						 | 
					e84d262f38 | ||
| 
						 | 
					29b192211d | ||
| 
						 | 
					583ca2fbac | ||
| 
						 | 
					82d25b0bee | ||
| 
						 | 
					181b3249b8 | ||
| 
						 | 
					8b38249df7 | ||
| 
						 | 
					01af8237db | ||
| 
						 | 
					2f4884c655 | ||
| 
						 | 
					c9ee2a1d24 | ||
| 
						 | 
					8d5ba6a5e4 | ||
| 
						 | 
					d3cfe019e3 | ||
| 
						 | 
					4c2ecd3f41 | ||
| 
						 | 
					d8ccdec501 | ||
| 
						 | 
					938f9f1326 | ||
| 
						 | 
					29bc21735d | ||
| 
						 | 
					ec7a7e4746 | ||
| 
						 | 
					0b0952b28c | ||
| 
						 | 
					9e52663261 | ||
| 
						 | 
					8f2adb76e4 | ||
| 
						 | 
					0d6c329477 | ||
| 
						 | 
					1c751f7253 | ||
| 
						 | 
					2264d22c69 | ||
| 
						 | 
					9fe72ea96e | ||
| 
						 | 
					48299810e0 | ||
| 
						 | 
					2a0ab8e549 | ||
| 
						 | 
					23a9d648d2 | ||
| 
						 | 
					a3029fa3f9 | ||
| 
						 | 
					387785b40c | ||
| 
						 | 
					03dea55131 | ||
| 
						 | 
					7b8aa989f6 | ||
| 
						 | 
					6ab6383144 | ||
| 
						 | 
					690d60f9d6 | ||
| 
						 | 
					da0de80afd | ||
| 
						 | 
					cd7ae788b1 | ||
| 
						 | 
					0d96cc53bf | ||
| 
						 | 
					66fc3c5b35 | ||
| 
						 | 
					5ab3eb8a38 | ||
| 
						 | 
					fec23b4acd | ||
| 
						 | 
					901622fee0 | ||
| 
						 | 
					527e622774 | ||
| 
						 | 
					7223b79fe8 | ||
| 
						 | 
					1ade41aeae | ||
| 
						 | 
					58dc579255 | ||
| 
						 | 
					370dac201b | ||
| 
						 | 
					2a763006db | ||
| 
						 | 
					522c66653b | ||
| 
						 | 
					b57f050b81 | ||
| 
						 | 
					41ebcf150a | ||
| 
						 | 
					1499def6ad | ||
| 
						 | 
					adbab0f5d7 | ||
| 88c88cac5b | |||
| 1ae38c98ad | |||
| 2d517cc594 | |||
| a9c82bd261 | |||
| 79aec86f5f | |||
| 9b3dfc7576 | |||
| 037ae74782 | |||
| b81c60a3ce | |||
| 363ac94c47 | |||
| 378f9e5095 | |||
| 659b494ee4 | |||
| 92965c6af2 | |||
| 70cb5aec9f | |||
| d59e77d5a2 | |||
| ff1b857ab0 | |||
| dbdccdb920 | |||
| fd3fef72d3 | |||
| 1890fd4f71 | |||
| 95af3cb515 | |||
| 3acebc451e | |||
| 
						 | 
					5111c9c8be | ||
| 
						 | 
					3ecb0e9d96 | ||
| 
						 | 
					b4a1766677 | ||
| 
						 | 
					241c6a5a08 | ||
| 
						 | 
					7c30633bde | ||
| 
						 | 
					81d3406305 | ||
| 
						 | 
					04f7537066 | ||
| 
						 | 
					6bf058ab5c | ||
| 
						 | 
					b771b5d25e | ||
| 
						 | 
					6e6ed4ea2c | ||
| 
						 | 
					a098f0a672 | ||
| 
						 | 
					cafadec146 | ||
| 
						 | 
					0940b63961 | ||
| 
						 | 
					a2dca94dca | ||
| 
						 | 
					085a8718e0 | ||
| 
						 | 
					271cc2caa0 | ||
| 
						 | 
					42b60ca5cd | ||
| 
						 | 
					4920322d0a | ||
| 
						 | 
					c7c1535ba9 | ||
| 
						 | 
					576f53f81b | ||
| 
						 | 
					c0e6247fb8 | ||
| 
						 | 
					3e85fdc779 | ||
| 
						 | 
					4833bcb710 | ||
| 
						 | 
					7d69d65dd2 | ||
| 
						 | 
					a098b3797a | ||
| 
						 | 
					7d03676ac2 | ||
| 
						 | 
					945b7a893e | ||
| 
						 | 
					ef028cb2b9 | ||
| 
						 | 
					4cfd0a1789 | ||
| 
						 | 
					7c57cf34a8 | ||
| 
						 | 
					019b590b4f | ||
| 
						 | 
					d82ae166a1 | ||
| 
						 | 
					ffaa67fb5d | ||
| 
						 | 
					a573a4ce71 | ||
| 
						 | 
					52d5a1fbf9 | ||
| 
						 | 
					4ad32401fd | ||
| 
						 | 
					f663ec80f5 | ||
| 
						 | 
					e55727d9e2 | ||
| 
						 | 
					4a178d01e3 | ||
| 
						 | 
					3d13833572 | ||
| 
						 | 
					31ec352b57 | ||
| 
						 | 
					940ef17f7b | ||
| 
						 | 
					ad3293da9d | ||
| 
						 | 
					3ffff7d32c | ||
| 
						 | 
					e646cfef0b | ||
| 
						 | 
					88b7cfe2fd | ||
| 
						 | 
					7201cabb43 | ||
| 
						 | 
					a8e2445c10 | ||
| 
						 | 
					69bf951866 | ||
| 
						 | 
					3061df4f13 | ||
| 
						 | 
					2ccb57ffb0 | ||
| 
						 | 
					847fce07bb | ||
| 
						 | 
					f481cde465 | ||
| 
						 | 
					bf114b39b7 | ||
| 
						 | 
					22d15fe395 | ||
| 
						 | 
					14977c7b2c | ||
| 
						 | 
					8d9bb20538 | ||
| 
						 | 
					6a977203ab | ||
| 
						 | 
					275bd56fe6 | ||
| 
						 | 
					2662709fed | ||
| 
						 | 
					64bea2a66e | ||
| 
						 | 
					6807614ac8 | ||
| 
						 | 
					676f2f4caa | ||
| 
						 | 
					a2f2d0ebef | ||
| 
						 | 
					b2113bff62 | ||
| 
						 | 
					892bd93471 | ||
| 
						 | 
					3ec0d554ed | ||
| 
						 | 
					976a5cedcb | ||
| 
						 | 
					107ce25801 | ||
| 
						 | 
					6350491f9f | ||
| 
						 | 
					c78f758202 | ||
| 
						 | 
					787c01b4be | ||
| 
						 | 
					826d7586b1 | ||
| 
						 | 
					84d20c52fa | ||
| 
						 | 
					b176874c2b | ||
| 
						 | 
					df2c38199c | ||
| 
						 | 
					ede2d5fd53 | ||
| 
						 | 
					d111a97521 | ||
| 
						 | 
					330768490a | ||
| 
						 | 
					74a1f66d26 | ||
| 
						 | 
					598774b0b1 | ||
| 
						 | 
					bf1d4a4001 | ||
| 
						 | 
					db85d1a48b | ||
| 
						 | 
					3ff7b47995 | ||
| 
						 | 
					8b03df7923 | ||
| 
						 | 
					98dc733240 | ||
| 
						 | 
					c02e3dffcf | ||
| 
						 | 
					1cdbcca7f7 | ||
| 
						 | 
					9b8acb83cb | ||
| 
						 | 
					7ca360be6a | ||
| 
						 | 
					6c4fab1adc | ||
| 
						 | 
					6551b1b97d | ||
| 
						 | 
					71d9bd4678 | ||
| 
						 | 
					1ad9ce09cb | ||
| 
						 | 
					d731277914 | ||
| 
						 | 
					24fe99cfa5 | ||
| 
						 | 
					2a373e7368 | ||
| 
						 | 
					68bacf5da4 | ||
| 
						 | 
					ed158ffdcb | ||
| 
						 | 
					fbb55e64dc | ||
| 
						 | 
					1521b8fac5 | ||
| 
						 | 
					97d466818a | ||
| 
						 | 
					c1888f8921 | ||
| 
						 | 
					db6049bab3 | ||
| 
						 | 
					5cc68bca6d | ||
| 
						 | 
					49e495f062 | ||
| 
						 | 
					1952d905d2 | ||
| 
						 | 
					2205ac9b58 | ||
| 
						 | 
					e9017767d1 | ||
| 
						 | 
					ad660b0ce8 | ||
| 
						 | 
					d15fdac27b | ||
| 
						 | 
					386881c283 | ||
| 
						 | 
					8cba10c4fe | ||
| 
						 | 
					f8ac3154e1 | ||
| 
						 | 
					df04133551 | ||
| 
						 | 
					99693d8ec0 | ||
| 
						 | 
					0e798dac50 | ||
| 
						 | 
					e6ac7d0da6 | ||
| 
						 | 
					9c71730d9c | ||
| 
						 | 
					4be954a6f3 | ||
| 
						 | 
					e9278111a6 | ||
| 
						 | 
					ed1e761052 | ||
| 
						 | 
					86b1e4ad5d | ||
| 
						 | 
					062c1afe85 | ||
| 
						 | 
					fa00980352 | ||
| 
						 | 
					2a93b17d71 | ||
| 
						 | 
					287aa3dea3 | ||
| 
						 | 
					8ab313e6cb | ||
| 
						 | 
					cccb54d38f | ||
| 
						 | 
					67940296d2 | ||
| 
						 | 
					67ebeca1f4 | ||
| 
						 | 
					b45e882559 | ||
| 
						 | 
					745bb58c59 | ||
| 
						 | 
					bf5a16f41b | ||
| 
						 | 
					bc12fb53be | ||
| 
						 | 
					0d83885b9b | ||
| 
						 | 
					de585a7234 | ||
| 
						 | 
					5c2980fb36 | ||
| 
						 | 
					e741a95cdb | ||
| 
						 | 
					19eb5239a6 | ||
| 
						 | 
					305f260503 | ||
| 
						 | 
					d1f6331ff8 | ||
| 
						 | 
					67b8215adf | ||
| 
						 | 
					58b36f2823 | ||
| 
						 | 
					2452d37acf | ||
| 
						 | 
					8e4ebbf622 | ||
| 
						 | 
					b85ca8674b | ||
| 
						 | 
					c63a1fef6c | ||
| 
						 | 
					66196da877 | ||
| 
						 | 
					e5c7dbe4cb | ||
| 
						 | 
					f72ceecc19 | ||
| 
						 | 
					ed787683f4 | ||
| 
						 | 
					d44fb976e4 | ||
| 
						 | 
					fb1b44e1d1 | ||
| 
						 | 
					d00109daf3 | ||
| 
						 | 
					367613a9d5 | ||
| 
						 | 
					b990fe42d3 | ||
| 
						 | 
					7d11c23eba | ||
| 
						 | 
					450fab437c | ||
| 
						 | 
					a4a249bab8 | ||
| 
						 | 
					d9c9f05cd2 | ||
| 
						 | 
					68f4189283 | ||
| 
						 | 
					0e0540af43 | ||
| 
						 | 
					555c5acb26 | ||
| 
						 | 
					b48e2cb3e5 | ||
| 
						 | 
					cf1c5f2186 | ||
| 
						 | 
					be38030395 | ||
| 
						 | 
					ad69c04951 | ||
| 
						 | 
					abd6c1d712 | ||
| 
						 | 
					a55f4c449c | ||
| 
						 | 
					1a4694c891 | ||
| 
						 | 
					b782248da7 | ||
| 
						 | 
					ae9a80c8f3 | ||
| 
						 | 
					918006302b | ||
| 
						 | 
					f30076e0f5 | ||
| 
						 | 
					78157b80d2 | ||
| 
						 | 
					4309309bc9 | ||
| 
						 | 
					0feab329c1 | ||
| 
						 | 
					1c32cd2d12 | ||
| 
						 | 
					a0f436b3e1 | ||
| 
						 | 
					6e5c873796 | ||
| 
						 | 
					3cdb43074b | ||
| 
						 | 
					11905339bb | ||
| 
						 | 
					301ef8dc05 | ||
| 
						 | 
					0e84db61de | ||
| 
						 | 
					21a7ff9010 | ||
| 5255ffc2f7 | |||
| fd1c579ec4 | |||
| 0f4adeea86 | |||
| 
						 | 
					be3b09b683 | ||
| 
						 | 
					7696f065f8 | ||
| 245f3adea3 | |||
| 21d08204b5 | |||
| 
						 | 
					02d1e93c78 | ||
| 1de4888599 | |||
| 
						 | 
					fbbce7817b | ||
| 
						 | 
					1fcbc7c08a | ||
| 
						 | 
					fd01f535a1 | ||
| 
						 | 
					6681c455d8 | ||
| 
						 | 
					4b88da8ff6 | ||
| 
						 | 
					ea55c94c73 | ||
| 
						 | 
					471e0c9d9b | ||
| 
						 | 
					2924ccd23b | ||
| 
						 | 
					6042d47700 | ||
| 
						 | 
					599a614480 | ||
| 
						 | 
					e2ddd7e4e6 | ||
| 
						 | 
					9a2ed2351d | ||
| 
						 | 
					2ec6899a18 | ||
| 
						 | 
					f17f48921d | ||
| 
						 | 
					cb21db672b | ||
| 
						 | 
					b591533ba4 | ||
| 
						 | 
					8d701e67d8 | ||
| 
						 | 
					8a11805fe8 | ||
| 
						 | 
					68bb4a51f1 | ||
| 
						 | 
					30be88c8ce | ||
| 
						 | 
					e524c865ea | ||
| 
						 | 
					9ea342c4c4 | ||
| 
						 | 
					b388e43f30 | ||
| 
						 | 
					e560f34215 | ||
| 
						 | 
					11a3767255 | ||
| 
						 | 
					0349ad9a29 | ||
| 
						 | 
					195f5b0794 | ||
| 
						 | 
					2816e3ea35 | ||
| 
						 | 
					15ca06aba8 | ||
| 
						 | 
					e9b3a65a0e | ||
| 
						 | 
					d8fac883d2 | ||
| 
						 | 
					ac49d3324d | ||
| 
						 | 
					3b77c0da83 | ||
| 
						 | 
					57f18b2244 | ||
| 
						 | 
					b49685aa82 | ||
| 
						 | 
					08e9ee67fe | ||
| 
						 | 
					94803f820a | ||
| 
						 | 
					898700d127 | ||
| 
						 | 
					cfcad2343d | ||
| 
						 | 
					aab0d54dfa | ||
| 
						 | 
					603641a589 | ||
| 
						 | 
					35d90f9c4e | ||
| 
						 | 
					3b8079ca87 | ||
| 
						 | 
					902ad0ea71 | ||
| 
						 | 
					9a8625f8b4 | ||
| 
						 | 
					c24f2f26c4 | ||
| 
						 | 
					14fe694fd3 | ||
| 
						 | 
					06c3af5d4d | ||
| 
						 | 
					8a21a7c803 | ||
| 
						 | 
					6497e7dbdd | ||
| 
						 | 
					b5dba2458a | ||
| 
						 | 
					b2a6ac19cb | ||
| 
						 | 
					d6dba8e1f1 | ||
| 
						 | 
					bc79d54284 | ||
| 
						 | 
					7fd44a55cb | ||
| 
						 | 
					f3df1e42b9 | ||
| 
						 | 
					4c0c75be91 | ||
| 
						 | 
					e6ca520a88 | ||
| 
						 | 
					2d249f38ff | ||
| 
						 | 
					4a17d0c07d | ||
| 
						 | 
					1d6b9db5f9 | ||
| 
						 | 
					d249bcdf94 | ||
| 
						 | 
					66413e15bb | ||
| 
						 | 
					828efb4c98 | ||
| 
						 | 
					06e048165e | ||
| 
						 | 
					c47ba64d49 | ||
| 
						 | 
					e4860ff67e | ||
| 
						 | 
					5f05b73366 | ||
| 
						 | 
					20ce1f5ef3 | ||
| 
						 | 
					4de43a301c | ||
| 
						 | 
					1a55212378 | ||
| 
						 | 
					c34e5579fc | ||
| 
						 | 
					cc8fc2df21 | ||
| 
						 | 
					cd902c6688 | ||
| 
						 | 
					84deb17e37 | ||
| 
						 | 
					158d3aacc8 | ||
| 
						 | 
					55a25aba83 | ||
| 
						 | 
					719fa239e0 | ||
| 
						 | 
					72d5c64c2d | ||
| 
						 | 
					e45fefe883 | ||
| 
						 | 
					8e82b87fb3 | ||
| 
						 | 
					59a1b52242 | ||
| 
						 | 
					3c54f3d39e | ||
| 
						 | 
					56f81fb30e | ||
| 
						 | 
					73fce1d8fb | ||
| 
						 | 
					3e7f8513eb | ||
| 
						 | 
					1561d0c81e | ||
| 
						 | 
					847ef2e95c | ||
| 
						 | 
					dbcd9cf004 | ||
| 
						 | 
					9654d59fc0 | ||
| 
						 | 
					b432266486 | ||
| 
						 | 
					73602b6c3d | ||
| 
						 | 
					9f5e6d6018 | ||
| 
						 | 
					62705cc9b9 | ||
| 
						 | 
					69fe3f8d76 | ||
| 
						 | 
					556d711ab6 | ||
| 
						 | 
					17749c6c0b | ||
| 
						 | 
					84024a143e | ||
| 
						 | 
					ae49c40ea5 | ||
| 
						 | 
					b90ffbc4f0 | ||
| 
						 | 
					ba6ac86bff | ||
| 
						 | 
					5d9c922b26 | ||
| 
						 | 
					208f69ae7e | ||
| 
						 | 
					3c14c64f4a | ||
| 
						 | 
					3bbde50f96 | ||
| 
						 | 
					65668f5bee | ||
| 
						 | 
					03e066c297 | ||
| 
						 | 
					e69d5b3351 | ||
| 
						 | 
					a39f539426 | ||
| 
						 | 
					6724ff38fe | ||
| 
						 | 
					c09994fd84 | ||
| 
						 | 
					1d0b06ac4a | ||
| 
						 | 
					7b2da3ba94 | ||
| 
						 | 
					c5bdc96542 | ||
| 
						 | 
					e28b87ce7b | ||
| 
						 | 
					0efb852839 | ||
| 
						 | 
					6dfd6058ef | ||
| 
						 | 
					c083ce748c | ||
| 
						 | 
					3388fcc6f3 | ||
| 
						 | 
					91128313fc | ||
| 
						 | 
					34212e86a5 | ||
| 
						 | 
					2de218782f | ||
| 
						 | 
					1a061f2d1f | ||
| 
						 | 
					de0b910e09 | ||
| 
						 | 
					d865ee2766 | ||
| 
						 | 
					9d246ad180 | ||
| 
						 | 
					2797d97537 | ||
| 
						 | 
					a09a04e1a7 | ||
| 
						 | 
					3f610619b5 | ||
| 
						 | 
					c309d97623 | ||
| 
						 | 
					93903b4938 | ||
| 
						 | 
					3c1a84011e | ||
| 
						 | 
					f9bfafa004 | ||
| 
						 | 
					f26249ab8b | ||
| 
						 | 
					4ec32bafa7 | ||
| 
						 | 
					e621fb616a | ||
| 
						 | 
					aaef5334ce | ||
| 
						 | 
					be976bd8ed | ||
| 
						 | 
					5f735b6e9d | ||
| 
						 | 
					f490dc3555 | ||
| 
						 | 
					cdf513c2c4 | ||
| 
						 | 
					eba6fd4f97 | ||
| 
						 | 
					1582654a9c | ||
| 
						 | 
					0ac55a0ec1 | ||
| 
						 | 
					3d58416d9b | ||
| 
						 | 
					279090889f | ||
| 
						 | 
					8953e2cd42 | ||
| 
						 | 
					a48bcf25e4 | ||
| 
						 | 
					2df71c6571 | ||
| 
						 | 
					89a1ab3f6e | ||
| 
						 | 
					021b461b0a | ||
| 
						 | 
					5c9adcf597 | ||
| 
						 | 
					92bb274d03 | ||
| 
						 | 
					95a4acff36 | ||
| 
						 | 
					50ea0969e6 | ||
| 
						 | 
					6acbcb6704 | ||
| 
						 | 
					6c9b9ea30d | ||
| 
						 | 
					a4374602e1 | ||
| 
						 | 
					610eb0cfda | ||
| 
						 | 
					4f798263b1 | ||
| 
						 | 
					a0dd8e1c42 | ||
| 
						 | 
					754925c32c | ||
| 
						 | 
					4956166f76 | 
							
								
								
									
										660
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										660
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,660 @@
 | 
			
		||||
# GNU AFFERO GENERAL PUBLIC LICENSE
 | 
			
		||||
 | 
			
		||||
Version 3, 19 November 2007
 | 
			
		||||
 | 
			
		||||
Copyright (C) 2007 Free Software Foundation, Inc.
 | 
			
		||||
<https://fsf.org/>
 | 
			
		||||
 | 
			
		||||
Everyone is permitted to copy and distribute verbatim copies of this
 | 
			
		||||
license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
## Preamble
 | 
			
		||||
 | 
			
		||||
The GNU Affero General Public License is a free, copyleft license for
 | 
			
		||||
software and other kinds of works, specifically designed to ensure
 | 
			
		||||
cooperation with the community in the case of network server software.
 | 
			
		||||
 | 
			
		||||
The licenses for most software and other practical works are designed
 | 
			
		||||
to take away your freedom to share and change the works. By contrast,
 | 
			
		||||
our General Public Licenses are intended to guarantee your freedom to
 | 
			
		||||
share and change all versions of a program--to make sure it remains
 | 
			
		||||
free software for all its users.
 | 
			
		||||
 | 
			
		||||
When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price. Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
them if you wish), that you receive source code or can get it if you
 | 
			
		||||
want it, that you can change the software or use pieces of it in new
 | 
			
		||||
free programs, and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
Developers that use our General Public Licenses protect your rights
 | 
			
		||||
with two steps: (1) assert copyright on the software, and (2) offer
 | 
			
		||||
you this License which gives you legal permission to copy, distribute
 | 
			
		||||
and/or modify the software.
 | 
			
		||||
 | 
			
		||||
A secondary benefit of defending all users' freedom is that
 | 
			
		||||
improvements made in alternate versions of the program, if they
 | 
			
		||||
receive widespread use, become available for other developers to
 | 
			
		||||
incorporate. Many developers of free software are heartened and
 | 
			
		||||
encouraged by the resulting cooperation. However, in the case of
 | 
			
		||||
software used on network servers, this result may fail to come about.
 | 
			
		||||
The GNU General Public License permits making a modified version and
 | 
			
		||||
letting the public access it on a server without ever releasing its
 | 
			
		||||
source code to the public.
 | 
			
		||||
 | 
			
		||||
The GNU Affero General Public License is designed specifically to
 | 
			
		||||
ensure that, in such cases, the modified source code becomes available
 | 
			
		||||
to the community. It requires the operator of a network server to
 | 
			
		||||
provide the source code of the modified version running there to the
 | 
			
		||||
users of that server. Therefore, public use of a modified version, on
 | 
			
		||||
a publicly accessible server, gives the public access to the source
 | 
			
		||||
code of the modified version.
 | 
			
		||||
 | 
			
		||||
An older license, called the Affero General Public License and
 | 
			
		||||
published by Affero, was designed to accomplish similar goals. This is
 | 
			
		||||
a different license, not a version of the Affero GPL, but Affero has
 | 
			
		||||
released a new version of the Affero GPL which permits relicensing
 | 
			
		||||
under this license.
 | 
			
		||||
 | 
			
		||||
The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
## TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
### 0. Definitions.
 | 
			
		||||
 | 
			
		||||
"This License" refers to version 3 of the GNU Affero General Public
 | 
			
		||||
License.
 | 
			
		||||
 | 
			
		||||
"Copyright" also means copyright-like laws that apply to other kinds
 | 
			
		||||
of works, such as semiconductor masks.
 | 
			
		||||
 | 
			
		||||
"The Program" refers to any copyrightable work licensed under this
 | 
			
		||||
License. Each licensee is addressed as "you". "Licensees" and
 | 
			
		||||
"recipients" may be individuals or organizations.
 | 
			
		||||
 | 
			
		||||
To "modify" a work means to copy from or adapt all or part of the work
 | 
			
		||||
in a fashion requiring copyright permission, other than the making of
 | 
			
		||||
an exact copy. The resulting work is called a "modified version" of
 | 
			
		||||
the earlier work or a work "based on" the earlier work.
 | 
			
		||||
 | 
			
		||||
A "covered work" means either the unmodified Program or a work based
 | 
			
		||||
on the Program.
 | 
			
		||||
 | 
			
		||||
To "propagate" a work means to do anything with it that, without
 | 
			
		||||
permission, would make you directly or secondarily liable for
 | 
			
		||||
infringement under applicable copyright law, except executing it on a
 | 
			
		||||
computer or modifying a private copy. Propagation includes copying,
 | 
			
		||||
distribution (with or without modification), making available to the
 | 
			
		||||
public, and in some countries other activities as well.
 | 
			
		||||
 | 
			
		||||
To "convey" a work means any kind of propagation that enables other
 | 
			
		||||
parties to make or receive copies. Mere interaction with a user
 | 
			
		||||
through a computer network, with no transfer of a copy, is not
 | 
			
		||||
conveying.
 | 
			
		||||
 | 
			
		||||
An interactive user interface displays "Appropriate Legal Notices" to
 | 
			
		||||
the extent that it includes a convenient and prominently visible
 | 
			
		||||
feature that (1) displays an appropriate copyright notice, and (2)
 | 
			
		||||
tells the user that there is no warranty for the work (except to the
 | 
			
		||||
extent that warranties are provided), that licensees may convey the
 | 
			
		||||
work under this License, and how to view a copy of this License. If
 | 
			
		||||
the interface presents a list of user commands or options, such as a
 | 
			
		||||
menu, a prominent item in the list meets this criterion.
 | 
			
		||||
 | 
			
		||||
### 1. Source Code.
 | 
			
		||||
 | 
			
		||||
The "source code" for a work means the preferred form of the work for
 | 
			
		||||
making modifications to it. "Object code" means any non-source form of
 | 
			
		||||
a work.
 | 
			
		||||
 | 
			
		||||
A "Standard Interface" means an interface that either is an official
 | 
			
		||||
standard defined by a recognized standards body, or, in the case of
 | 
			
		||||
interfaces specified for a particular programming language, one that
 | 
			
		||||
is widely used among developers working in that language.
 | 
			
		||||
 | 
			
		||||
The "System Libraries" of an executable work include anything, other
 | 
			
		||||
than the work as a whole, that (a) is included in the normal form of
 | 
			
		||||
packaging a Major Component, but which is not part of that Major
 | 
			
		||||
Component, and (b) serves only to enable use of the work with that
 | 
			
		||||
Major Component, or to implement a Standard Interface for which an
 | 
			
		||||
implementation is available to the public in source code form. A
 | 
			
		||||
"Major Component", in this context, means a major essential component
 | 
			
		||||
(kernel, window system, and so on) of the specific operating system
 | 
			
		||||
(if any) on which the executable work runs, or a compiler used to
 | 
			
		||||
produce the work, or an object code interpreter used to run it.
 | 
			
		||||
 | 
			
		||||
The "Corresponding Source" for a work in object code form means all
 | 
			
		||||
the source code needed to generate, install, and (for an executable
 | 
			
		||||
work) run the object code and to modify the work, including scripts to
 | 
			
		||||
control those activities. However, it does not include the work's
 | 
			
		||||
System Libraries, or general-purpose tools or generally available free
 | 
			
		||||
programs which are used unmodified in performing those activities but
 | 
			
		||||
which are not part of the work. For example, Corresponding Source
 | 
			
		||||
includes interface definition files associated with source files for
 | 
			
		||||
the work, and the source code for shared libraries and dynamically
 | 
			
		||||
linked subprograms that the work is specifically designed to require,
 | 
			
		||||
such as by intimate data communication or control flow between those
 | 
			
		||||
subprograms and other parts of the work.
 | 
			
		||||
 | 
			
		||||
The Corresponding Source need not include anything that users can
 | 
			
		||||
regenerate automatically from other parts of the Corresponding Source.
 | 
			
		||||
 | 
			
		||||
The Corresponding Source for a work in source code form is that same
 | 
			
		||||
work.
 | 
			
		||||
 | 
			
		||||
### 2. Basic Permissions.
 | 
			
		||||
 | 
			
		||||
All rights granted under this License are granted for the term of
 | 
			
		||||
copyright on the Program, and are irrevocable provided the stated
 | 
			
		||||
conditions are met. This License explicitly affirms your unlimited
 | 
			
		||||
permission to run the unmodified Program. The output from running a
 | 
			
		||||
covered work is covered by this License only if the output, given its
 | 
			
		||||
content, constitutes a covered work. This License acknowledges your
 | 
			
		||||
rights of fair use or other equivalent, as provided by copyright law.
 | 
			
		||||
 | 
			
		||||
You may make, run and propagate covered works that you do not convey,
 | 
			
		||||
without conditions so long as your license otherwise remains in force.
 | 
			
		||||
You may convey covered works to others for the sole purpose of having
 | 
			
		||||
them make modifications exclusively for you, or provide you with
 | 
			
		||||
facilities for running those works, provided that you comply with the
 | 
			
		||||
terms of this License in conveying all material for which you do not
 | 
			
		||||
control copyright. Those thus making or running the covered works for
 | 
			
		||||
you must do so exclusively on your behalf, under your direction and
 | 
			
		||||
control, on terms that prohibit them from making any copies of your
 | 
			
		||||
copyrighted material outside their relationship with you.
 | 
			
		||||
 | 
			
		||||
Conveying under any other circumstances is permitted solely under the
 | 
			
		||||
conditions stated below. Sublicensing is not allowed; section 10 makes
 | 
			
		||||
it unnecessary.
 | 
			
		||||
 | 
			
		||||
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
 | 
			
		||||
 | 
			
		||||
No covered work shall be deemed part of an effective technological
 | 
			
		||||
measure under any applicable law fulfilling obligations under article
 | 
			
		||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
 | 
			
		||||
similar laws prohibiting or restricting circumvention of such
 | 
			
		||||
measures.
 | 
			
		||||
 | 
			
		||||
When you convey a covered work, you waive any legal power to forbid
 | 
			
		||||
circumvention of technological measures to the extent such
 | 
			
		||||
circumvention is effected by exercising rights under this License with
 | 
			
		||||
respect to the covered work, and you disclaim any intention to limit
 | 
			
		||||
operation or modification of the work as a means of enforcing, against
 | 
			
		||||
the work's users, your or third parties' legal rights to forbid
 | 
			
		||||
circumvention of technological measures.
 | 
			
		||||
 | 
			
		||||
### 4. Conveying Verbatim Copies.
 | 
			
		||||
 | 
			
		||||
You may convey verbatim copies of the Program's source code as you
 | 
			
		||||
receive it, in any medium, provided that you conspicuously and
 | 
			
		||||
appropriately publish on each copy an appropriate copyright notice;
 | 
			
		||||
keep intact all notices stating that this License and any
 | 
			
		||||
non-permissive terms added in accord with section 7 apply to the code;
 | 
			
		||||
keep intact all notices of the absence of any warranty; and give all
 | 
			
		||||
recipients a copy of this License along with the Program.
 | 
			
		||||
 | 
			
		||||
You may charge any price or no price for each copy that you convey,
 | 
			
		||||
and you may offer support or warranty protection for a fee.
 | 
			
		||||
 | 
			
		||||
### 5. Conveying Modified Source Versions.
 | 
			
		||||
 | 
			
		||||
You may convey a work based on the Program, or the modifications to
 | 
			
		||||
produce it from the Program, in the form of source code under the
 | 
			
		||||
terms of section 4, provided that you also meet all of these
 | 
			
		||||
conditions:
 | 
			
		||||
 | 
			
		||||
-   a) The work must carry prominent notices stating that you modified
 | 
			
		||||
    it, and giving a relevant date.
 | 
			
		||||
-   b) The work must carry prominent notices stating that it is
 | 
			
		||||
    released under this License and any conditions added under
 | 
			
		||||
    section 7. This requirement modifies the requirement in section 4
 | 
			
		||||
    to "keep intact all notices".
 | 
			
		||||
-   c) You must license the entire work, as a whole, under this
 | 
			
		||||
    License to anyone who comes into possession of a copy. This
 | 
			
		||||
    License will therefore apply, along with any applicable section 7
 | 
			
		||||
    additional terms, to the whole of the work, and all its parts,
 | 
			
		||||
    regardless of how they are packaged. This License gives no
 | 
			
		||||
    permission to license the work in any other way, but it does not
 | 
			
		||||
    invalidate such permission if you have separately received it.
 | 
			
		||||
-   d) If the work has interactive user interfaces, each must display
 | 
			
		||||
    Appropriate Legal Notices; however, if the Program has interactive
 | 
			
		||||
    interfaces that do not display Appropriate Legal Notices, your
 | 
			
		||||
    work need not make them do so.
 | 
			
		||||
 | 
			
		||||
A compilation of a covered work with other separate and independent
 | 
			
		||||
works, which are not by their nature extensions of the covered work,
 | 
			
		||||
and which are not combined with it such as to form a larger program,
 | 
			
		||||
in or on a volume of a storage or distribution medium, is called an
 | 
			
		||||
"aggregate" if the compilation and its resulting copyright are not
 | 
			
		||||
used to limit the access or legal rights of the compilation's users
 | 
			
		||||
beyond what the individual works permit. Inclusion of a covered work
 | 
			
		||||
in an aggregate does not cause this License to apply to the other
 | 
			
		||||
parts of the aggregate.
 | 
			
		||||
 | 
			
		||||
### 6. Conveying Non-Source Forms.
 | 
			
		||||
 | 
			
		||||
You may convey a covered work in object code form under the terms of
 | 
			
		||||
sections 4 and 5, provided that you also convey the machine-readable
 | 
			
		||||
Corresponding Source under the terms of this License, in one of these
 | 
			
		||||
ways:
 | 
			
		||||
 | 
			
		||||
-   a) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by the
 | 
			
		||||
    Corresponding Source fixed on a durable physical medium
 | 
			
		||||
    customarily used for software interchange.
 | 
			
		||||
-   b) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by a
 | 
			
		||||
    written offer, valid for at least three years and valid for as
 | 
			
		||||
    long as you offer spare parts or customer support for that product
 | 
			
		||||
    model, to give anyone who possesses the object code either (1) a
 | 
			
		||||
    copy of the Corresponding Source for all the software in the
 | 
			
		||||
    product that is covered by this License, on a durable physical
 | 
			
		||||
    medium customarily used for software interchange, for a price no
 | 
			
		||||
    more than your reasonable cost of physically performing this
 | 
			
		||||
    conveying of source, or (2) access to copy the Corresponding
 | 
			
		||||
    Source from a network server at no charge.
 | 
			
		||||
-   c) Convey individual copies of the object code with a copy of the
 | 
			
		||||
    written offer to provide the Corresponding Source. This
 | 
			
		||||
    alternative is allowed only occasionally and noncommercially, and
 | 
			
		||||
    only if you received the object code with such an offer, in accord
 | 
			
		||||
    with subsection 6b.
 | 
			
		||||
-   d) Convey the object code by offering access from a designated
 | 
			
		||||
    place (gratis or for a charge), and offer equivalent access to the
 | 
			
		||||
    Corresponding Source in the same way through the same place at no
 | 
			
		||||
    further charge. You need not require recipients to copy the
 | 
			
		||||
    Corresponding Source along with the object code. If the place to
 | 
			
		||||
    copy the object code is a network server, the Corresponding Source
 | 
			
		||||
    may be on a different server (operated by you or a third party)
 | 
			
		||||
    that supports equivalent copying facilities, provided you maintain
 | 
			
		||||
    clear directions next to the object code saying where to find the
 | 
			
		||||
    Corresponding Source. Regardless of what server hosts the
 | 
			
		||||
    Corresponding Source, you remain obligated to ensure that it is
 | 
			
		||||
    available for as long as needed to satisfy these requirements.
 | 
			
		||||
-   e) Convey the object code using peer-to-peer transmission,
 | 
			
		||||
    provided you inform other peers where the object code and
 | 
			
		||||
    Corresponding Source of the work are being offered to the general
 | 
			
		||||
    public at no charge under subsection 6d.
 | 
			
		||||
 | 
			
		||||
A separable portion of the object code, whose source code is excluded
 | 
			
		||||
from the Corresponding Source as a System Library, need not be
 | 
			
		||||
included in conveying the object code work.
 | 
			
		||||
 | 
			
		||||
A "User Product" is either (1) a "consumer product", which means any
 | 
			
		||||
tangible personal property which is normally used for personal,
 | 
			
		||||
family, or household purposes, or (2) anything designed or sold for
 | 
			
		||||
incorporation into a dwelling. In determining whether a product is a
 | 
			
		||||
consumer product, doubtful cases shall be resolved in favor of
 | 
			
		||||
coverage. For a particular product received by a particular user,
 | 
			
		||||
"normally used" refers to a typical or common use of that class of
 | 
			
		||||
product, regardless of the status of the particular user or of the way
 | 
			
		||||
in which the particular user actually uses, or expects or is expected
 | 
			
		||||
to use, the product. A product is a consumer product regardless of
 | 
			
		||||
whether the product has substantial commercial, industrial or
 | 
			
		||||
non-consumer uses, unless such uses represent the only significant
 | 
			
		||||
mode of use of the product.
 | 
			
		||||
 | 
			
		||||
"Installation Information" for a User Product means any methods,
 | 
			
		||||
procedures, authorization keys, or other information required to
 | 
			
		||||
install and execute modified versions of a covered work in that User
 | 
			
		||||
Product from a modified version of its Corresponding Source. The
 | 
			
		||||
information must suffice to ensure that the continued functioning of
 | 
			
		||||
the modified object code is in no case prevented or interfered with
 | 
			
		||||
solely because modification has been made.
 | 
			
		||||
 | 
			
		||||
If you convey an object code work under this section in, or with, or
 | 
			
		||||
specifically for use in, a User Product, and the conveying occurs as
 | 
			
		||||
part of a transaction in which the right of possession and use of the
 | 
			
		||||
User Product is transferred to the recipient in perpetuity or for a
 | 
			
		||||
fixed term (regardless of how the transaction is characterized), the
 | 
			
		||||
Corresponding Source conveyed under this section must be accompanied
 | 
			
		||||
by the Installation Information. But this requirement does not apply
 | 
			
		||||
if neither you nor any third party retains the ability to install
 | 
			
		||||
modified object code on the User Product (for example, the work has
 | 
			
		||||
been installed in ROM).
 | 
			
		||||
 | 
			
		||||
The requirement to provide Installation Information does not include a
 | 
			
		||||
requirement to continue to provide support service, warranty, or
 | 
			
		||||
updates for a work that has been modified or installed by the
 | 
			
		||||
recipient, or for the User Product in which it has been modified or
 | 
			
		||||
installed. Access to a network may be denied when the modification
 | 
			
		||||
itself materially and adversely affects the operation of the network
 | 
			
		||||
or violates the rules and protocols for communication across the
 | 
			
		||||
network.
 | 
			
		||||
 | 
			
		||||
Corresponding Source conveyed, and Installation Information provided,
 | 
			
		||||
in accord with this section must be in a format that is publicly
 | 
			
		||||
documented (and with an implementation available to the public in
 | 
			
		||||
source code form), and must require no special password or key for
 | 
			
		||||
unpacking, reading or copying.
 | 
			
		||||
 | 
			
		||||
### 7. Additional Terms.
 | 
			
		||||
 | 
			
		||||
"Additional permissions" are terms that supplement the terms of this
 | 
			
		||||
License by making exceptions from one or more of its conditions.
 | 
			
		||||
Additional permissions that are applicable to the entire Program shall
 | 
			
		||||
be treated as though they were included in this License, to the extent
 | 
			
		||||
that they are valid under applicable law. If additional permissions
 | 
			
		||||
apply only to part of the Program, that part may be used separately
 | 
			
		||||
under those permissions, but the entire Program remains governed by
 | 
			
		||||
this License without regard to the additional permissions.
 | 
			
		||||
 | 
			
		||||
When you convey a copy of a covered work, you may at your option
 | 
			
		||||
remove any additional permissions from that copy, or from any part of
 | 
			
		||||
it. (Additional permissions may be written to require their own
 | 
			
		||||
removal in certain cases when you modify the work.) You may place
 | 
			
		||||
additional permissions on material, added by you to a covered work,
 | 
			
		||||
for which you have or can give appropriate copyright permission.
 | 
			
		||||
 | 
			
		||||
Notwithstanding any other provision of this License, for material you
 | 
			
		||||
add to a covered work, you may (if authorized by the copyright holders
 | 
			
		||||
of that material) supplement the terms of this License with terms:
 | 
			
		||||
 | 
			
		||||
-   a) Disclaiming warranty or limiting liability differently from the
 | 
			
		||||
    terms of sections 15 and 16 of this License; or
 | 
			
		||||
-   b) Requiring preservation of specified reasonable legal notices or
 | 
			
		||||
    author attributions in that material or in the Appropriate Legal
 | 
			
		||||
    Notices displayed by works containing it; or
 | 
			
		||||
-   c) Prohibiting misrepresentation of the origin of that material,
 | 
			
		||||
    or requiring that modified versions of such material be marked in
 | 
			
		||||
    reasonable ways as different from the original version; or
 | 
			
		||||
-   d) Limiting the use for publicity purposes of names of licensors
 | 
			
		||||
    or authors of the material; or
 | 
			
		||||
-   e) Declining to grant rights under trademark law for use of some
 | 
			
		||||
    trade names, trademarks, or service marks; or
 | 
			
		||||
-   f) Requiring indemnification of licensors and authors of that
 | 
			
		||||
    material by anyone who conveys the material (or modified versions
 | 
			
		||||
    of it) with contractual assumptions of liability to the recipient,
 | 
			
		||||
    for any liability that these contractual assumptions directly
 | 
			
		||||
    impose on those licensors and authors.
 | 
			
		||||
 | 
			
		||||
All other non-permissive additional terms are considered "further
 | 
			
		||||
restrictions" within the meaning of section 10. If the Program as you
 | 
			
		||||
received it, or any part of it, contains a notice stating that it is
 | 
			
		||||
governed by this License along with a term that is a further
 | 
			
		||||
restriction, you may remove that term. If a license document contains
 | 
			
		||||
a further restriction but permits relicensing or conveying under this
 | 
			
		||||
License, you may add to a covered work material governed by the terms
 | 
			
		||||
of that license document, provided that the further restriction does
 | 
			
		||||
not survive such relicensing or conveying.
 | 
			
		||||
 | 
			
		||||
If you add terms to a covered work in accord with this section, you
 | 
			
		||||
must place, in the relevant source files, a statement of the
 | 
			
		||||
additional terms that apply to those files, or a notice indicating
 | 
			
		||||
where to find the applicable terms.
 | 
			
		||||
 | 
			
		||||
Additional terms, permissive or non-permissive, may be stated in the
 | 
			
		||||
form of a separately written license, or stated as exceptions; the
 | 
			
		||||
above requirements apply either way.
 | 
			
		||||
 | 
			
		||||
### 8. Termination.
 | 
			
		||||
 | 
			
		||||
You may not propagate or modify a covered work except as expressly
 | 
			
		||||
provided under this License. Any attempt otherwise to propagate or
 | 
			
		||||
modify it is void, and will automatically terminate your rights under
 | 
			
		||||
this License (including any patent licenses granted under the third
 | 
			
		||||
paragraph of section 11).
 | 
			
		||||
 | 
			
		||||
However, if you cease all violation of this License, then your license
 | 
			
		||||
from a particular copyright holder is reinstated (a) provisionally,
 | 
			
		||||
unless and until the copyright holder explicitly and finally
 | 
			
		||||
terminates your license, and (b) permanently, if the copyright holder
 | 
			
		||||
fails to notify you of the violation by some reasonable means prior to
 | 
			
		||||
60 days after the cessation.
 | 
			
		||||
 | 
			
		||||
Moreover, your license from a particular copyright holder is
 | 
			
		||||
reinstated permanently if the copyright holder notifies you of the
 | 
			
		||||
violation by some reasonable means, this is the first time you have
 | 
			
		||||
received notice of violation of this License (for any work) from that
 | 
			
		||||
copyright holder, and you cure the violation prior to 30 days after
 | 
			
		||||
your receipt of the notice.
 | 
			
		||||
 | 
			
		||||
Termination of your rights under this section does not terminate the
 | 
			
		||||
licenses of parties who have received copies or rights from you under
 | 
			
		||||
this License. If your rights have been terminated and not permanently
 | 
			
		||||
reinstated, you do not qualify to receive new licenses for the same
 | 
			
		||||
material under section 10.
 | 
			
		||||
 | 
			
		||||
### 9. Acceptance Not Required for Having Copies.
 | 
			
		||||
 | 
			
		||||
You are not required to accept this License in order to receive or run
 | 
			
		||||
a copy of the Program. Ancillary propagation of a covered work
 | 
			
		||||
occurring solely as a consequence of using peer-to-peer transmission
 | 
			
		||||
to receive a copy likewise does not require acceptance. However,
 | 
			
		||||
nothing other than this License grants you permission to propagate or
 | 
			
		||||
modify any covered work. These actions infringe copyright if you do
 | 
			
		||||
not accept this License. Therefore, by modifying or propagating a
 | 
			
		||||
covered work, you indicate your acceptance of this License to do so.
 | 
			
		||||
 | 
			
		||||
### 10. Automatic Licensing of Downstream Recipients.
 | 
			
		||||
 | 
			
		||||
Each time you convey a covered work, the recipient automatically
 | 
			
		||||
receives a license from the original licensors, to run, modify and
 | 
			
		||||
propagate that work, subject to this License. You are not responsible
 | 
			
		||||
for enforcing compliance by third parties with this License.
 | 
			
		||||
 | 
			
		||||
An "entity transaction" is a transaction transferring control of an
 | 
			
		||||
organization, or substantially all assets of one, or subdividing an
 | 
			
		||||
organization, or merging organizations. If propagation of a covered
 | 
			
		||||
work results from an entity transaction, each party to that
 | 
			
		||||
transaction who receives a copy of the work also receives whatever
 | 
			
		||||
licenses to the work the party's predecessor in interest had or could
 | 
			
		||||
give under the previous paragraph, plus a right to possession of the
 | 
			
		||||
Corresponding Source of the work from the predecessor in interest, if
 | 
			
		||||
the predecessor has it or can get it with reasonable efforts.
 | 
			
		||||
 | 
			
		||||
You may not impose any further restrictions on the exercise of the
 | 
			
		||||
rights granted or affirmed under this License. For example, you may
 | 
			
		||||
not impose a license fee, royalty, or other charge for exercise of
 | 
			
		||||
rights granted under this License, and you may not initiate litigation
 | 
			
		||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
 | 
			
		||||
any patent claim is infringed by making, using, selling, offering for
 | 
			
		||||
sale, or importing the Program or any portion of it.
 | 
			
		||||
 | 
			
		||||
### 11. Patents.
 | 
			
		||||
 | 
			
		||||
A "contributor" is a copyright holder who authorizes use under this
 | 
			
		||||
License of the Program or a work on which the Program is based. The
 | 
			
		||||
work thus licensed is called the contributor's "contributor version".
 | 
			
		||||
 | 
			
		||||
A contributor's "essential patent claims" are all patent claims owned
 | 
			
		||||
or controlled by the contributor, whether already acquired or
 | 
			
		||||
hereafter acquired, that would be infringed by some manner, permitted
 | 
			
		||||
by this License, of making, using, or selling its contributor version,
 | 
			
		||||
but do not include claims that would be infringed only as a
 | 
			
		||||
consequence of further modification of the contributor version. For
 | 
			
		||||
purposes of this definition, "control" includes the right to grant
 | 
			
		||||
patent sublicenses in a manner consistent with the requirements of
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
 | 
			
		||||
patent license under the contributor's essential patent claims, to
 | 
			
		||||
make, use, sell, offer for sale, import and otherwise run, modify and
 | 
			
		||||
propagate the contents of its contributor version.
 | 
			
		||||
 | 
			
		||||
In the following three paragraphs, a "patent license" is any express
 | 
			
		||||
agreement or commitment, however denominated, not to enforce a patent
 | 
			
		||||
(such as an express permission to practice a patent or covenant not to
 | 
			
		||||
sue for patent infringement). To "grant" such a patent license to a
 | 
			
		||||
party means to make such an agreement or commitment not to enforce a
 | 
			
		||||
patent against the party.
 | 
			
		||||
 | 
			
		||||
If you convey a covered work, knowingly relying on a patent license,
 | 
			
		||||
and the Corresponding Source of the work is not available for anyone
 | 
			
		||||
to copy, free of charge and under the terms of this License, through a
 | 
			
		||||
publicly available network server or other readily accessible means,
 | 
			
		||||
then you must either (1) cause the Corresponding Source to be so
 | 
			
		||||
available, or (2) arrange to deprive yourself of the benefit of the
 | 
			
		||||
patent license for this particular work, or (3) arrange, in a manner
 | 
			
		||||
consistent with the requirements of this License, to extend the patent
 | 
			
		||||
license to downstream recipients. "Knowingly relying" means you have
 | 
			
		||||
actual knowledge that, but for the patent license, your conveying the
 | 
			
		||||
covered work in a country, or your recipient's use of the covered work
 | 
			
		||||
in a country, would infringe one or more identifiable patents in that
 | 
			
		||||
country that you have reason to believe are valid.
 | 
			
		||||
 | 
			
		||||
If, pursuant to or in connection with a single transaction or
 | 
			
		||||
arrangement, you convey, or propagate by procuring conveyance of, a
 | 
			
		||||
covered work, and grant a patent license to some of the parties
 | 
			
		||||
receiving the covered work authorizing them to use, propagate, modify
 | 
			
		||||
or convey a specific copy of the covered work, then the patent license
 | 
			
		||||
you grant is automatically extended to all recipients of the covered
 | 
			
		||||
work and works based on it.
 | 
			
		||||
 | 
			
		||||
A patent license is "discriminatory" if it does not include within the
 | 
			
		||||
scope of its coverage, prohibits the exercise of, or is conditioned on
 | 
			
		||||
the non-exercise of one or more of the rights that are specifically
 | 
			
		||||
granted under this License. You may not convey a covered work if you
 | 
			
		||||
are a party to an arrangement with a third party that is in the
 | 
			
		||||
business of distributing software, under which you make payment to the
 | 
			
		||||
third party based on the extent of your activity of conveying the
 | 
			
		||||
work, and under which the third party grants, to any of the parties
 | 
			
		||||
who would receive the covered work from you, a discriminatory patent
 | 
			
		||||
license (a) in connection with copies of the covered work conveyed by
 | 
			
		||||
you (or copies made from those copies), or (b) primarily for and in
 | 
			
		||||
connection with specific products or compilations that contain the
 | 
			
		||||
covered work, unless you entered into that arrangement, or that patent
 | 
			
		||||
license was granted, prior to 28 March 2007.
 | 
			
		||||
 | 
			
		||||
Nothing in this License shall be construed as excluding or limiting
 | 
			
		||||
any implied license or other defenses to infringement that may
 | 
			
		||||
otherwise be available to you under applicable patent law.
 | 
			
		||||
 | 
			
		||||
### 12. No Surrender of Others' Freedom.
 | 
			
		||||
 | 
			
		||||
If conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License. If you cannot convey a
 | 
			
		||||
covered work so as to satisfy simultaneously your obligations under
 | 
			
		||||
this License and any other pertinent obligations, then as a
 | 
			
		||||
consequence you may not convey it at all. For example, if you agree to
 | 
			
		||||
terms that obligate you to collect a royalty for further conveying
 | 
			
		||||
from those to whom you convey the Program, the only way you could
 | 
			
		||||
satisfy both those terms and this License would be to refrain entirely
 | 
			
		||||
from conveying the Program.
 | 
			
		||||
 | 
			
		||||
### 13. Remote Network Interaction; Use with the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
Notwithstanding any other provision of this License, if you modify the
 | 
			
		||||
Program, your modified version must prominently offer all users
 | 
			
		||||
interacting with it remotely through a computer network (if your
 | 
			
		||||
version supports such interaction) an opportunity to receive the
 | 
			
		||||
Corresponding Source of your version by providing access to the
 | 
			
		||||
Corresponding Source from a network server at no charge, through some
 | 
			
		||||
standard or customary means of facilitating copying of software. This
 | 
			
		||||
Corresponding Source shall include the Corresponding Source for any
 | 
			
		||||
work covered by version 3 of the GNU General Public License that is
 | 
			
		||||
incorporated pursuant to the following paragraph.
 | 
			
		||||
 | 
			
		||||
Notwithstanding any other provision of this License, you have
 | 
			
		||||
permission to link or combine any covered work with a work licensed
 | 
			
		||||
under version 3 of the GNU General Public License into a single
 | 
			
		||||
combined work, and to convey the resulting work. The terms of this
 | 
			
		||||
License will continue to apply to the part which is the covered work,
 | 
			
		||||
but the work with which it is combined will remain governed by version
 | 
			
		||||
3 of the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
### 14. Revised Versions of this License.
 | 
			
		||||
 | 
			
		||||
The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the GNU Affero General Public License from time to time. Such new
 | 
			
		||||
versions will be similar in spirit to the present version, but may
 | 
			
		||||
differ in detail to address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
Each version is given a distinguishing version number. If the Program
 | 
			
		||||
specifies that a certain numbered version of the GNU Affero General
 | 
			
		||||
Public License "or any later version" applies to it, you have the
 | 
			
		||||
option of following the terms and conditions either of that numbered
 | 
			
		||||
version or of any later version published by the Free Software
 | 
			
		||||
Foundation. If the Program does not specify a version number of the
 | 
			
		||||
GNU Affero General Public License, you may choose any version ever
 | 
			
		||||
published by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
If the Program specifies that a proxy can decide which future versions
 | 
			
		||||
of the GNU Affero General Public License can be used, that proxy's
 | 
			
		||||
public statement of acceptance of a version permanently authorizes you
 | 
			
		||||
to choose that version for the Program.
 | 
			
		||||
 | 
			
		||||
Later license versions may give you additional or different
 | 
			
		||||
permissions. However, no additional obligations are imposed on any
 | 
			
		||||
author or copyright holder as a result of your choosing to follow a
 | 
			
		||||
later version.
 | 
			
		||||
 | 
			
		||||
### 15. Disclaimer of Warranty.
 | 
			
		||||
 | 
			
		||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
 | 
			
		||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
 | 
			
		||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
 | 
			
		||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
 | 
			
		||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
 | 
			
		||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
 | 
			
		||||
CORRECTION.
 | 
			
		||||
 | 
			
		||||
### 16. Limitation of Liability.
 | 
			
		||||
 | 
			
		||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
 | 
			
		||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 | 
			
		||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
 | 
			
		||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
 | 
			
		||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
 | 
			
		||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
 | 
			
		||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
 | 
			
		||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
### 17. Interpretation of Sections 15 and 16.
 | 
			
		||||
 | 
			
		||||
If the disclaimer of warranty and limitation of liability provided
 | 
			
		||||
above cannot be given local legal effect according to their terms,
 | 
			
		||||
reviewing courts shall apply local law that most closely approximates
 | 
			
		||||
an absolute waiver of all civil liability in connection with the
 | 
			
		||||
Program, unless a warranty or assumption of liability accompanies a
 | 
			
		||||
copy of the Program in return for a fee.
 | 
			
		||||
 | 
			
		||||
END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
## How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these
 | 
			
		||||
terms.
 | 
			
		||||
 | 
			
		||||
To do so, attach the following notices to the program. It is safest to
 | 
			
		||||
attach them to the start of each source file to most effectively state
 | 
			
		||||
the exclusion of warranty; and each file should have at least the
 | 
			
		||||
"copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
        <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
        Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
        This program is free software: you can redistribute it and/or modify
 | 
			
		||||
        it under the terms of the GNU Affero General Public License as
 | 
			
		||||
        published by the Free Software Foundation, either version 3 of the
 | 
			
		||||
        License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
        This program is distributed in the hope that it will be useful,
 | 
			
		||||
        but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
        GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
        You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
        along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper
 | 
			
		||||
mail.
 | 
			
		||||
 | 
			
		||||
If your software can interact with users remotely through a computer
 | 
			
		||||
network, you should also make sure that it provides a way for users to
 | 
			
		||||
get its source. For example, if your program is a web application, its
 | 
			
		||||
interface could display a "Source" link that leads users to an archive
 | 
			
		||||
of the code. There are many ways you could offer source, and different
 | 
			
		||||
solutions will be better for different programs; see section 13 for
 | 
			
		||||
the specific requirements.
 | 
			
		||||
 | 
			
		||||
You should also get your employer (if you work as a programmer) or
 | 
			
		||||
school, if any, to sign a "copyright disclaimer" for the program, if
 | 
			
		||||
necessary. For more information on this, and how to apply and follow
 | 
			
		||||
the GNU AGPL, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
@@ -16,6 +16,7 @@ type Config struct {
 | 
			
		||||
	Port          string
 | 
			
		||||
	LokiUrl       string
 | 
			
		||||
	LogLevel      string
 | 
			
		||||
	Whitelist     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) GetUrl() string {
 | 
			
		||||
@@ -37,19 +38,11 @@ func GetConfig() *Config {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string, logLevel string) *Config {
 | 
			
		||||
	/*once.Do(func() {
 | 
			
		||||
		instance = &Config{
 | 
			
		||||
			MongoUrl:      mongoUrl,
 | 
			
		||||
			MongoDatabase: database,
 | 
			
		||||
			NATSUrl:       natsUrl,
 | 
			
		||||
			LokiUrl:       lokiUrl,
 | 
			
		||||
			LogLevel:      logLevel,
 | 
			
		||||
		}
 | 
			
		||||
	})*/
 | 
			
		||||
	GetConfig().MongoUrl = mongoUrl
 | 
			
		||||
	GetConfig().MongoDatabase = database
 | 
			
		||||
	GetConfig().NATSUrl = natsUrl
 | 
			
		||||
	GetConfig().LokiUrl = lokiUrl
 | 
			
		||||
	GetConfig().LogLevel = logLevel
 | 
			
		||||
	GetConfig().Whitelist = true
 | 
			
		||||
	return GetConfig()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,12 +26,12 @@ import (
 | 
			
		||||
func GetConfLoader() *onion.Onion {
 | 
			
		||||
	logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
 | 
			
		||||
	AppName := GetAppName()
 | 
			
		||||
	EnvPrefix := strings.ToUpper(AppName[0:2]+AppName[3:]) + "_"
 | 
			
		||||
	EnvPrefix := "OC_"
 | 
			
		||||
	defaultConfigFile := "/etc/oc/" + AppName[3:] + ".json"
 | 
			
		||||
	localConfigFile := "./" + AppName[3:] + ".json"
 | 
			
		||||
	var configFile string
 | 
			
		||||
	var o *onion.Onion
 | 
			
		||||
	l3 := onion.NewEnvLayerPrefix("_", EnvPrefix)
 | 
			
		||||
	l3 := GetEnvVarLayer(EnvPrefix)
 | 
			
		||||
	l2, err := onion.NewFileLayer(localConfigFile, nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		logger.Info().Msg("Local config file found " + localConfigFile + ", overriding default file")
 | 
			
		||||
@@ -54,3 +54,17 @@ func GetConfLoader() *onion.Onion {
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetEnvVarLayer(prefix string) onion.Layer {
 | 
			
		||||
	envVars := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	for _, e := range os.Environ() {
 | 
			
		||||
		pair := strings.SplitN(e, "=", 2)
 | 
			
		||||
		key := pair[0]
 | 
			
		||||
		if strings.HasPrefix(key, prefix) {
 | 
			
		||||
			envVars[strings.TrimPrefix(key, prefix)] = pair[1]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return onion.NewMapLayer(envVars)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/static"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson"
 | 
			
		||||
@@ -49,7 +48,7 @@ func (m *MongoDB) Init(collections []string, config MongoConf) {
 | 
			
		||||
	mngoCollections = collections
 | 
			
		||||
	mngoConfig = config
 | 
			
		||||
	if err := m.createClient(config.GetUrl(), false); err != nil {
 | 
			
		||||
		m.Logger.Error().Msg(err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -132,10 +131,6 @@ func (m *MongoDB) prepareDB(list_collection []string, config MongoConf) {
 | 
			
		||||
		new_collection := mngoDB.Collection(collection_name)
 | 
			
		||||
		if _, exists := collectionMap[collection_name]; !exists {
 | 
			
		||||
			m.createCollection(collection_name, new_collection)
 | 
			
		||||
			if collection_name == "peer" {
 | 
			
		||||
				id, p := static.GetMyLocalBsonPeer()
 | 
			
		||||
				m.StoreOne(p, id, collection_name)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			CollectionMap[collection_name] = new_collection
 | 
			
		||||
		}
 | 
			
		||||
@@ -175,12 +170,12 @@ func (m *MongoDB) DeleteOne(id string, collection_name string) (int64, int, erro
 | 
			
		||||
	filter := bson.M{"_id": id}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}})
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	result, err := targetDBCollection.DeleteOne(MngoCtx, filter, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		return 0, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return result.DeletedCount, 200, nil
 | 
			
		||||
@@ -196,12 +191,12 @@ func (m *MongoDB) DeleteMultiple(f map[string]interface{}, collection_name strin
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	opts := options.Delete().SetHint(bson.D{{Key: "_id", Value: 1}})
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	result, err := targetDBCollection.DeleteMany(MngoCtx, filter, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		return 0, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return result.DeletedCount, 200, nil
 | 
			
		||||
@@ -219,11 +214,11 @@ func (m *MongoDB) UpdateMultiple(set interface{}, filter map[string]interface{},
 | 
			
		||||
		f = append(f, bson.E{Key: k, Value: v})
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
	res, err := targetDBCollection.UpdateMany(MngoCtx, f, dbs.InputToBson(doc, true))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
 | 
			
		||||
		return 0, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res.UpsertedCount, 200, nil
 | 
			
		||||
@@ -238,11 +233,11 @@ func (m *MongoDB) UpdateOne(set interface{}, id string, collection_name string)
 | 
			
		||||
	bson.Unmarshal(b, &doc)
 | 
			
		||||
	filter := bson.M{"_id": id}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
	_, err := targetDBCollection.UpdateOne(MngoCtx, filter, dbs.InputToBson(doc, true))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't update resource: " + err.Error())
 | 
			
		||||
		return "", 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return id, 200, nil
 | 
			
		||||
@@ -257,12 +252,12 @@ func (m *MongoDB) StoreOne(obj interface{}, id string, collection_name string) (
 | 
			
		||||
	bson.Unmarshal(b, &doc)
 | 
			
		||||
	doc["_id"] = id
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	_, err := targetDBCollection.InsertOne(MngoCtx, doc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't insert resource: " + err.Error())
 | 
			
		||||
		return "", 409, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -275,13 +270,12 @@ func (m *MongoDB) LoadOne(id string, collection_name string) (*mongo.SingleResul
 | 
			
		||||
	}
 | 
			
		||||
	filter := bson.M{"_id": id}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	res := targetDBCollection.FindOne(MngoCtx, filter)
 | 
			
		||||
	if res.Err() != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't find resource " + id + ". Error : " + res.Err().Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't find resource " + id + ". Error : " + res.Err().Error())
 | 
			
		||||
		err := res.Err()
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -293,7 +287,7 @@ func (m *MongoDB) Search(filters *dbs.Filters, collection_name string) (*mongo.C
 | 
			
		||||
		return nil, 503, err
 | 
			
		||||
	}
 | 
			
		||||
	opts := options.Find()
 | 
			
		||||
	opts.SetLimit(100)
 | 
			
		||||
	opts.SetLimit(1000)
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
	orList := bson.A{}
 | 
			
		||||
	andList := bson.A{}
 | 
			
		||||
@@ -319,8 +313,8 @@ func (m *MongoDB) Search(filters *dbs.Filters, collection_name string) (*mongo.C
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	// defer cancel()
 | 
			
		||||
	if cursor, err := targetDBCollection.Find(
 | 
			
		||||
		MngoCtx,
 | 
			
		||||
		f,
 | 
			
		||||
@@ -342,12 +336,12 @@ func (m *MongoDB) LoadFilter(filter map[string]interface{}, collection_name stri
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	res, err := targetDBCollection.Find(MngoCtx, f)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't find any resources. Error : " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't find any resources. Error : " + err.Error())
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
@@ -359,12 +353,12 @@ func (m *MongoDB) LoadAll(collection_name string) (*mongo.Cursor, int, error) {
 | 
			
		||||
	}
 | 
			
		||||
	targetDBCollection := CollectionMap[collection_name]
 | 
			
		||||
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	MngoCtx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
	//defer cancel()
 | 
			
		||||
 | 
			
		||||
	res, err := targetDBCollection.Find(MngoCtx, bson.D{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Logger.Error().Msg("Couldn't find any resources. Error : " + err.Error())
 | 
			
		||||
		// m.Logger.Error().Msg("Couldn't find any resources. Error : " + err.Error())
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ func init() {
 | 
			
		||||
		{Key: "example", Value: "text"}},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	IndexesMap["datacenter"] = append(IndexesMap["datacenter"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
	IndexesMap["compute"] = append(IndexesMap["compute"], mongo.IndexModel{Keys: bson.D{
 | 
			
		||||
		{Key: "description", Value: "text"},
 | 
			
		||||
		{Key: "example", Value: "text"},
 | 
			
		||||
		{Key: "owner", Value: "text"}},
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ abstract Resource{
 | 
			
		||||
    +icon: string
 | 
			
		||||
    +description: string
 | 
			
		||||
    +graphic: GraphicElement
 | 
			
		||||
    +element: DataResource/ProcessingResource/StorageResource/Workflow/DatacenterResource
 | 
			
		||||
    +element: DataResource/ProcessingResource/StorageResource/Workflow/ComputeResource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DataResource {
 | 
			
		||||
@@ -31,7 +31,7 @@ class StorageResource {
 | 
			
		||||
    +capacity: int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DatacenterResource {
 | 
			
		||||
class ComputeResource {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +name: string
 | 
			
		||||
 | 
			
		||||
@@ -96,7 +96,7 @@ class UserWorkflows {
 | 
			
		||||
 | 
			
		||||
class DatacenterWorkflows {
 | 
			
		||||
    +UUID: int
 | 
			
		||||
    +datacenter: DatacenterResource
 | 
			
		||||
    +compute: ComputeResource
 | 
			
		||||
    +workflows: Workflow[]
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
@@ -159,7 +159,7 @@ DatacenterWorkflows "1" o-- "0..*" Workflow
 | 
			
		||||
Resource<|-- DataResource
 | 
			
		||||
Resource<|-- ProcessingResource
 | 
			
		||||
Resource<|-- StorageResource
 | 
			
		||||
Resource<|-- DatacenterResource
 | 
			
		||||
Resource<|-- ComputeResource
 | 
			
		||||
Resource<|-- Workflow
 | 
			
		||||
 | 
			
		||||
ResourceSet "1" o-- "0..*" Ressource
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										325
									
								
								doc/order_model.puml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								doc/order_model.puml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,325 @@
 | 
			
		||||
@startuml
 | 
			
		||||
 | 
			
		||||
class AbstractObject {
 | 
			
		||||
  ID string 
 | 
			
		||||
  Name string
 | 
			
		||||
  IsDraft bool // is consider as a draft
 | 
			
		||||
  UpdateDate date 
 | 
			
		||||
  LastPeerWriter string 
 | 
			
		||||
  CreatorID string 
 | 
			
		||||
  AccessMode int // public or private
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AbstractObject ^-- AbstractResource
 | 
			
		||||
AbstractObject ^-- Order
 | 
			
		||||
AbstractObject ^-- Booking
 | 
			
		||||
AbstractObject ^-- BuyingStatus
 | 
			
		||||
AbstractObject ^-- WorkflowExecution
 | 
			
		||||
AbstractObject ^-- Workflow
 | 
			
		||||
 | 
			
		||||
class AbstractResource {
 | 
			
		||||
  Logo string 
 | 
			
		||||
  Description string 
 | 
			
		||||
  ShortDescription string 
 | 
			
		||||
  Owners []string 
 | 
			
		||||
  UsageRestrictions string 
 | 
			
		||||
  
 | 
			
		||||
  VerifyAuth(request) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AbstractResource "1 " --* "many  " ResourceInstanceITF
 | 
			
		||||
AbstractCustomizedResource "1  " --* "1  " ResourceInstanceITF
 | 
			
		||||
 | 
			
		||||
AbstractResource ^-- ComputeResource
 | 
			
		||||
AbstractResource ^-- DataResource
 | 
			
		||||
AbstractResource ^-- ProcessingResource
 | 
			
		||||
AbstractResource ^-- StorageResource
 | 
			
		||||
AbstractResource ^-- WorkflowResource
 | 
			
		||||
class ComputeResource {
 | 
			
		||||
  Architecture string 
 | 
			
		||||
  Infrastructure string
 | 
			
		||||
}
 | 
			
		||||
class DataResource {
 | 
			
		||||
  Type string 
 | 
			
		||||
  Quality string 
 | 
			
		||||
  OpenData bool 
 | 
			
		||||
  Static bool 
 | 
			
		||||
  UpdatePeriod date 
 | 
			
		||||
  PersonalData bool 
 | 
			
		||||
  AnonymizedPersonalData bool
 | 
			
		||||
  SizeGB float64
 | 
			
		||||
  Licence string 
 | 
			
		||||
  Example string
 | 
			
		||||
}
 | 
			
		||||
ProcessingResource "1  " *-- "1  " ProcessingUsage
 | 
			
		||||
class ProcessingUsage {
 | 
			
		||||
  CPUs map[string]CPU 
 | 
			
		||||
  GPUs map[string]GPU 
 | 
			
		||||
  RAM RAM
 | 
			
		||||
  StorageGB float64 
 | 
			
		||||
  Hypothesis string 
 | 
			
		||||
  ScalingModel string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ProcessingResource {
 | 
			
		||||
  Infrastructure string 
 | 
			
		||||
  Service bool 
 | 
			
		||||
  Usage ProcessingUsage
 | 
			
		||||
  OpenSource bool 
 | 
			
		||||
  License string 
 | 
			
		||||
  Maturity string 
 | 
			
		||||
}
 | 
			
		||||
class StorageResource {
 | 
			
		||||
  Type string 
 | 
			
		||||
  Accronym string
 | 
			
		||||
}
 | 
			
		||||
WorkflowResource "1  " --* "many  " ComputeResource
 | 
			
		||||
WorkflowResource "1  " --* "many  " DataResource
 | 
			
		||||
WorkflowResource "1  " --* "many  " ProcessingResource
 | 
			
		||||
WorkflowResource "1  " --* "many  " StorageResource
 | 
			
		||||
class WorkflowResource {
 | 
			
		||||
  WorkflowID string 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ExploitResourceSet {}
 | 
			
		||||
 | 
			
		||||
AbstractCustomizedResource --^ AbstractResource
 | 
			
		||||
AbstractCustomizedResource --* ExploitResourceSet
 | 
			
		||||
ExploitResourceSet ^-- CustomizedComputeResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedDataResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedProcessingResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedStorageResource
 | 
			
		||||
ExploitResourceSet ^-- CustomizedWorkflowResource
 | 
			
		||||
class AbstractCustomizedResource {
 | 
			
		||||
// A customized resource is an 
 | 
			
		||||
// extended abstract resource not use in catalog
 | 
			
		||||
  ExplicitBookingDurationS float64 
 | 
			
		||||
  UsageStart date 
 | 
			
		||||
  UsageEnd date 
 | 
			
		||||
  SelectedPricing string
 | 
			
		||||
}
 | 
			
		||||
class CustomizedComputeResource {
 | 
			
		||||
  CPUsLocated map[string]float64
 | 
			
		||||
  GPUsLocated map[string]float64
 | 
			
		||||
  RAMLocated float64
 | 
			
		||||
}
 | 
			
		||||
class CustomizedDataResource {
 | 
			
		||||
  StorageGB float64
 | 
			
		||||
}
 | 
			
		||||
class CustomizedProcessingResource {
 | 
			
		||||
  Container Container
 | 
			
		||||
}
 | 
			
		||||
class CustomizedStorageResource {
 | 
			
		||||
  StorageGB bool
 | 
			
		||||
}
 | 
			
		||||
class CustomizedWorkflowResource {}
 | 
			
		||||
 | 
			
		||||
interface ResourceInstanceITF {
 | 
			
		||||
  GetID() string 
 | 
			
		||||
  VerifyPartnership() bool // eval if there is one partnership per peer groups in every instance
 | 
			
		||||
  GetPeerGroups() []ResourcePartnerITF, []map[string][]string 
 | 
			
		||||
  ClearPeerGroups()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResourceInstanceITF -- ResourceInstance
 | 
			
		||||
ResourceInstance ^-- ComputeResourceInstance
 | 
			
		||||
ResourceInstance ^-- StorageResourceInstance
 | 
			
		||||
ResourceInstance "many  " --* "1  " ResourcePartnerITF
 | 
			
		||||
class ResourceInstance {
 | 
			
		||||
  ID string 
 | 
			
		||||
  Location Geopoint
 | 
			
		||||
  Country CountryCode 
 | 
			
		||||
  AccessProtocol string 
 | 
			
		||||
}
 | 
			
		||||
class ComputeResourceInstance {
 | 
			
		||||
  SecurityLevel string 
 | 
			
		||||
  PowerSource string 
 | 
			
		||||
  CPUs map[string]CPU 
 | 
			
		||||
  GPUs map[string]GPU 
 | 
			
		||||
  RAM RAM
 | 
			
		||||
}
 | 
			
		||||
class StorageResourceInstance {
 | 
			
		||||
  Local bool 
 | 
			
		||||
  SecurityLevel string 
 | 
			
		||||
  SizeType string 
 | 
			
		||||
  SizeGB int 
 | 
			
		||||
  Encryption bool 
 | 
			
		||||
  Redundancy string 
 | 
			
		||||
  Throughput string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResourcePartnerITF -- ResourcePartnership
 | 
			
		||||
ResourcePartnership ^-- ComputeResourcePartnership
 | 
			
		||||
ResourcePartnership ^-- DataResourcePartnership
 | 
			
		||||
ResourcePartnership ^-- StorageResourcePartnership
 | 
			
		||||
 | 
			
		||||
interface ResourcePartnerITF {
 | 
			
		||||
  GetPricing(id string) PricingProfileITF
 | 
			
		||||
  GetPeerGroups() []ResourcePartnerITF, []map[string][]string 
 | 
			
		||||
  ClearPeerGroups()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResourcePartnership "many  " --*  "1  " PricingProfileITF
 | 
			
		||||
class ResourcePartnership{
 | 
			
		||||
  Namespace string 
 | 
			
		||||
  PeerGroups map[string][]string 
 | 
			
		||||
}
 | 
			
		||||
class ComputeResourcePartnership {
 | 
			
		||||
  MaxAllowedCPUsCores map[string]int 
 | 
			
		||||
  MaxAllowedGPUsMemoryGB map[string]float64 
 | 
			
		||||
  RAMSizeGB float64
 | 
			
		||||
}
 | 
			
		||||
class DataResourcePartnership {
 | 
			
		||||
  MaxDownloadableGBAllowed float64
 | 
			
		||||
  PersonalDataAllowed bool
 | 
			
		||||
  AnonymizedPersonalDataAllowed bool
 | 
			
		||||
}
 | 
			
		||||
class StorageResourcePartnership {
 | 
			
		||||
  MaxSizeGBAllowed float64
 | 
			
		||||
  OnlyEncryptedAllowed bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RefundType -- AccessPricingProfile
 | 
			
		||||
enum RefundType {
 | 
			
		||||
  REFUND_DEAD_END
 | 
			
		||||
  REFUND_ON_ERROR
 | 
			
		||||
  REFUND_ON_EARLY_END
 | 
			
		||||
}
 | 
			
		||||
PricingProfileITF -- AccessPricingProfile
 | 
			
		||||
PricingProfileITF -- ExploitPricingProfile
 | 
			
		||||
PricingProfileITF -- WorkflowResourcePricingProfile
 | 
			
		||||
AccessPricingProfile ^-- DataResourcePricingProfile
 | 
			
		||||
AccessPricingProfile ^-- ProcessingResourcePricingProfile
 | 
			
		||||
ExploitPricingProfile ^-- ComputeResourcePricingProfile
 | 
			
		||||
ExploitPricingProfile ^-- StorageResourcePricingProfile
 | 
			
		||||
interface PricingProfileITF {
 | 
			
		||||
  GetPrice(quantity float64, val float64, start date, end date, request) float64
 | 
			
		||||
  IsPurchased() bool
 | 
			
		||||
}
 | 
			
		||||
class AccessPricingProfile {
 | 
			
		||||
  ID string 
 | 
			
		||||
  Pricing PricingStrategy
 | 
			
		||||
  DefaultRefundType RefundType 
 | 
			
		||||
  RefundRatio int // percentage of refund on price
 | 
			
		||||
}
 | 
			
		||||
class DataResourcePricingProfile {}
 | 
			
		||||
class ProcessingResourcePricingProfile {}
 | 
			
		||||
 | 
			
		||||
ExploitPrivilegeStrategy -- ExploitPricingProfile
 | 
			
		||||
enum ExploitPrivilegeStrategy {
 | 
			
		||||
  BASIC
 | 
			
		||||
  GARANTED_ON_DELAY
 | 
			
		||||
  GARANTED
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
AccessPricingProfile --* PricingStrategy
 | 
			
		||||
AccessPricingProfile ^-- ExploitPricingProfile
 | 
			
		||||
class ExploitPricingProfile {
 | 
			
		||||
  AdditionnalRefundTypes RefundTypeint 
 | 
			
		||||
  PrivilegeStrategy ExploitPrivilegeStrategy 
 | 
			
		||||
  GarantedDelaySecond int 
 | 
			
		||||
  Exceeding bool 
 | 
			
		||||
  ExceedingRatio int // percentage of Exceeding based on price
 | 
			
		||||
}
 | 
			
		||||
class ComputeResourcePricingProfile {
 | 
			
		||||
  OverrideCPUsPrices map[string]float64
 | 
			
		||||
  OverrideGPUsPrices map[string]float64
 | 
			
		||||
  OverrideRAMPrice float64
 | 
			
		||||
}
 | 
			
		||||
class StorageResourcePricingProfile {}
 | 
			
		||||
WorkflowResourcePricingProfile "1  " --* "many  " ExploitResourceSet
 | 
			
		||||
 | 
			
		||||
class WorkflowResourcePricingProfile {
 | 
			
		||||
  ID string 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BuyingStrategy -- PricingStrategy
 | 
			
		||||
enum BuyingStrategy {
 | 
			
		||||
  UNLIMITED 
 | 
			
		||||
  SUBSCRIPTION 
 | 
			
		||||
  PAY_PER_USE
 | 
			
		||||
}
 | 
			
		||||
Strategy -- TimePricingStrategy
 | 
			
		||||
Strategy "0-1  " *-- " " PricingStrategy
 | 
			
		||||
interface Strategy {
 | 
			
		||||
  GetStrategy () string 
 | 
			
		||||
  GetStrategyValue() int
 | 
			
		||||
}
 | 
			
		||||
enum TimePricingStrategy {
 | 
			
		||||
  ONCE 
 | 
			
		||||
  PER_SECOND 
 | 
			
		||||
  PER_MINUTE
 | 
			
		||||
  PER_HOUR
 | 
			
		||||
  PER_DAY
 | 
			
		||||
  PER_WEEK
 | 
			
		||||
  PER_MONTH
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class PricingStrategy {
 | 
			
		||||
  Price float64 
 | 
			
		||||
  BuyingStrategy 
 | 
			
		||||
  TimePricingStrategy TimePricingStrategy
 | 
			
		||||
  OverrideStrategy Strategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
PeerOrder "many  " *-- "1  " Order
 | 
			
		||||
PeerItemOrder "many  " *-- "1  " PeerOrder
 | 
			
		||||
PricedItemITF "many  " *-- "1  " PeerItemOrder
 | 
			
		||||
 | 
			
		||||
PricedItemITF -- AbstractCustomizedResource
 | 
			
		||||
 | 
			
		||||
class Order {
 | 
			
		||||
  OrderBy string 
 | 
			
		||||
  WorkflowExecutionIDs []string
 | 
			
		||||
  Status string 
 | 
			
		||||
  Total float64
 | 
			
		||||
}
 | 
			
		||||
class PeerOrder {
 | 
			
		||||
  PeerID string 
 | 
			
		||||
  Error string 
 | 
			
		||||
  Status string 
 | 
			
		||||
  BillingAddress string 
 | 
			
		||||
  Total float64
 | 
			
		||||
}
 | 
			
		||||
class PeerItemOrder {
 | 
			
		||||
  Quantity int 
 | 
			
		||||
  BuyingStatus string 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BuyingStatus {}
 | 
			
		||||
 | 
			
		||||
WorkflowExecution "many  " --* "1  " Workflow
 | 
			
		||||
Workflow "1  " --* "many  " WorkflowScheduler
 | 
			
		||||
WorkflowScheduler "1  " --* "many  " WorkflowExecution
 | 
			
		||||
 | 
			
		||||
class WorkflowExecution {
 | 
			
		||||
  ExecDate date 
 | 
			
		||||
  EndDate date 
 | 
			
		||||
  State string 
 | 
			
		||||
  WorkflowID string 
 | 
			
		||||
  
 | 
			
		||||
  ToBookings() []Booking
 | 
			
		||||
}
 | 
			
		||||
class WorkflowScheduler* {
 | 
			
		||||
  Message string 
 | 
			
		||||
  Warning string 
 | 
			
		||||
  Start date 
 | 
			
		||||
  End date 
 | 
			
		||||
  DurationS float64
 | 
			
		||||
  Cron string
 | 
			
		||||
  
 | 
			
		||||
  Schedules(workflowID string, request) []WorkflowExecution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Workflow "1  " --* "many  " ExploitResourceSet
 | 
			
		||||
 | 
			
		||||
class Workflow {}
 | 
			
		||||
 | 
			
		||||
interface PricedItemITF {
 | 
			
		||||
  getPrice(request) float64, error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@enduml
 | 
			
		||||
							
								
								
									
										29
									
								
								doc/paymentflowV1.puml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								doc/paymentflowV1.puml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
@startuml
 | 
			
		||||
user -> client : schedule 
 | 
			
		||||
client -> OrderAPIP1 : check book
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : check book
 | 
			
		||||
datacenterAPIP2 -> OrderAPIP1 : send ok
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : generate draft book
 | 
			
		||||
OrderAPIP1 -> client : send ok 
 | 
			
		||||
client -> OrderAPIP1 : send scheduler
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : draft executions
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : draft order
 | 
			
		||||
OrderAPIP1 -> client : send drafted order
 | 
			
		||||
client -> user : 
 | 
			
		||||
user -> client : select pricing profile
 | 
			
		||||
client -> OrderAPIP1 : update order 
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : check book
 | 
			
		||||
datacenterAPIP2 -> OrderAPIP1 : send ok
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : generate draft book
 | 
			
		||||
OrderAPIP1 -> client : send order
 | 
			
		||||
user -> client : order
 | 
			
		||||
client -> OrderAPIP1 : order 
 | 
			
		||||
OrderAPIP1 -> PaymentAPIBCP1 : send payment
 | 
			
		||||
PaymentAPIBCP1 -> OrderAPIP1 : send ok 
 | 
			
		||||
OrderAPIP1 -> datacenterAPIP2 : undraft booking
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : undraft execution
 | 
			
		||||
OrderAPIP1 -> OrderAPIP1 : undraft order
 | 
			
		||||
OrderAPIP1 -> client : send ok 
 | 
			
		||||
client -> client : redirect
 | 
			
		||||
@enduml
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										423
									
								
								entrypoint.go
									
									
									
									
									
								
							
							
						
						
									
										423
									
								
								entrypoint.go
									
									
									
									
									
								
							@@ -1,8 +1,12 @@
 | 
			
		||||
package oclib
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
 | 
			
		||||
@@ -11,20 +15,21 @@ import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/live"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/data"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/processing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/storage"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/resources/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	w2 "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	shared_workspace "cloud.o-forge.io/core/oc-lib/models/workspace/shared"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace/shared/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	beego "github.com/beego/beego/v2/server/web"
 | 
			
		||||
	"github.com/beego/beego/v2/server/web/context"
 | 
			
		||||
	"github.com/goraz/onion"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
@@ -36,28 +41,32 @@ type LibDataEnum int
 | 
			
		||||
// init accessible constant to retrieve data from the database
 | 
			
		||||
const (
 | 
			
		||||
	INVALID             LibDataEnum = iota
 | 
			
		||||
	DATA_RESOURCE                   = utils.DATA_RESOURCE
 | 
			
		||||
	PROCESSING_RESOURCE             = utils.PROCESSING_RESOURCE
 | 
			
		||||
	STORAGE_RESOURCE                = utils.STORAGE_RESOURCE
 | 
			
		||||
	DATACENTER_RESOURCE             = utils.DATACENTER_RESOURCE
 | 
			
		||||
	WORKFLOW_RESOURCE               = utils.WORKFLOW_RESOURCE
 | 
			
		||||
	WORKFLOW                        = utils.WORKFLOW
 | 
			
		||||
	WORKSPACE                       = utils.WORKSPACE
 | 
			
		||||
	WORKFLOW_EXECUTION              = utils.WORKFLOW_EXECUTION
 | 
			
		||||
	PEER                            = utils.PEER
 | 
			
		||||
	SHARED_WORKSPACE                = utils.SHARED_WORKSPACE
 | 
			
		||||
	RULE                            = utils.RULE
 | 
			
		||||
	BOOKING                         = utils.BOOKING
 | 
			
		||||
	DATA_RESOURCE                   = tools.DATA_RESOURCE
 | 
			
		||||
	PROCESSING_RESOURCE             = tools.PROCESSING_RESOURCE
 | 
			
		||||
	STORAGE_RESOURCE                = tools.STORAGE_RESOURCE
 | 
			
		||||
	COMPUTE_RESOURCE                = tools.COMPUTE_RESOURCE
 | 
			
		||||
	WORKFLOW_RESOURCE               = tools.WORKFLOW_RESOURCE
 | 
			
		||||
	WORKFLOW                        = tools.WORKFLOW
 | 
			
		||||
	WORKSPACE                       = tools.WORKSPACE
 | 
			
		||||
	WORKFLOW_EXECUTION              = tools.WORKFLOW_EXECUTION
 | 
			
		||||
	PEER                            = tools.PEER
 | 
			
		||||
	COLLABORATIVE_AREA              = tools.COLLABORATIVE_AREA
 | 
			
		||||
	RULE                            = tools.RULE
 | 
			
		||||
	BOOKING                         = tools.BOOKING
 | 
			
		||||
	ORDER                           = tools.ORDER
 | 
			
		||||
	LIVE_DATACENTER                 = tools.LIVE_DATACENTER
 | 
			
		||||
	LIVE_STORAGE                    = tools.LIVE_STORAGE
 | 
			
		||||
	PURCHASE_RESOURCE               = tools.PURCHASE_RESOURCE
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// will turn into standards api hostnames
 | 
			
		||||
func (d LibDataEnum) API() string {
 | 
			
		||||
	return utils.DefaultAPI[d]
 | 
			
		||||
	return tools.DefaultAPI[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// will turn into standards name
 | 
			
		||||
func (d LibDataEnum) String() string {
 | 
			
		||||
	return utils.Str[d]
 | 
			
		||||
	return tools.Str[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// will turn into enum index
 | 
			
		||||
@@ -65,6 +74,20 @@ func (d LibDataEnum) EnumIndex() int {
 | 
			
		||||
	return int(d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsQueryParamsEquals(input *context.BeegoInput, name string, val interface{}) bool {
 | 
			
		||||
	path := strings.Split(input.URI(), "?")
 | 
			
		||||
	if len(path) >= 2 {
 | 
			
		||||
		uri := strings.Split(path[1], "&")
 | 
			
		||||
		for _, val := range uri {
 | 
			
		||||
			kv := strings.Split(val, "=")
 | 
			
		||||
			if kv[0] == name && fmt.Sprintf("%v", val) == kv[1] {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// model to define the shallow data structure
 | 
			
		||||
type LibDataShallow struct {
 | 
			
		||||
	Data []utils.ShallowDBObject `bson:"data" json:"data"`
 | 
			
		||||
@@ -79,29 +102,75 @@ type LibData struct {
 | 
			
		||||
	Err  string         `bson:"error" json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// here is the singleton variable to store the paths that api will use
 | 
			
		||||
var paths map[LibDataEnum]string = map[LibDataEnum]string{}
 | 
			
		||||
 | 
			
		||||
// to get the paths
 | 
			
		||||
func GetPaths() map[LibDataEnum]string {
 | 
			
		||||
	return paths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// to get the path
 | 
			
		||||
func GetPath(collection LibDataEnum) string {
 | 
			
		||||
	return paths[collection]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// to add the path
 | 
			
		||||
func AddPath(collection LibDataEnum, path string) {
 | 
			
		||||
	paths[collection] = path
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Init(appName string) {
 | 
			
		||||
func InitDaemon(appName string) {
 | 
			
		||||
	config.SetAppName(appName) // set the app name to the logger to define the main log chan
 | 
			
		||||
	// create a temporary console logger for init
 | 
			
		||||
	logs.SetLogger(logs.CreateLogger("main"))
 | 
			
		||||
	// Load the right config file
 | 
			
		||||
	o := GetConfLoader()
 | 
			
		||||
 | 
			
		||||
	// feed the library with the loaded config
 | 
			
		||||
	SetConfig(
 | 
			
		||||
		o.GetStringDefault("MONGO_URL", "mongodb://127.0.0.1:27017"),
 | 
			
		||||
		o.GetStringDefault("MONGO_DATABASE", "DC_myDC"),
 | 
			
		||||
		o.GetStringDefault("NATS_URL", "nats://localhost:4222"),
 | 
			
		||||
		o.GetStringDefault("LOKI_URL", ""),
 | 
			
		||||
		o.GetStringDefault("LOG_LEVEL", "info"),
 | 
			
		||||
	)
 | 
			
		||||
	// Beego init
 | 
			
		||||
	beego.BConfig.AppName = appName
 | 
			
		||||
	beego.BConfig.Listen.HTTPPort = o.GetIntDefault("port", 8080)
 | 
			
		||||
	beego.BConfig.WebConfig.DirectoryIndex = true
 | 
			
		||||
	beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type IDTokenClaims struct {
 | 
			
		||||
	UserID string   `json:"user_id"`
 | 
			
		||||
	PeerID string   `json:"peer_id"`
 | 
			
		||||
	Groups []string `json:"groups"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SessionClaims struct
 | 
			
		||||
type SessionClaims struct {
 | 
			
		||||
	AccessToken map[string]interface{} `json:"access_token"`
 | 
			
		||||
	IDToken     IDTokenClaims          `json:"id_token"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Claims struct
 | 
			
		||||
type Claims struct {
 | 
			
		||||
	Session SessionClaims `json:"session"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExtractTokenInfo(request http.Request) (string, string, []string) {
 | 
			
		||||
	reqToken := request.Header.Get("Authorization")
 | 
			
		||||
	splitToken := strings.Split(reqToken, "Bearer ")
 | 
			
		||||
	if len(splitToken) < 2 {
 | 
			
		||||
		reqToken = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		reqToken = splitToken[1]
 | 
			
		||||
	}
 | 
			
		||||
	if reqToken != "" {
 | 
			
		||||
		token := strings.Split(reqToken, ".")
 | 
			
		||||
		if len(token) > 2 {
 | 
			
		||||
			bytes, err := base64.StdEncoding.DecodeString(token[2])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", "", []string{}
 | 
			
		||||
			}
 | 
			
		||||
			var c Claims
 | 
			
		||||
			err = json.Unmarshal(bytes, &c)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", "", []string{}
 | 
			
		||||
			}
 | 
			
		||||
			return c.Session.IDToken.UserID, c.Session.IDToken.PeerID, c.Session.IDToken.Groups
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", "", []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Init(appName string) {
 | 
			
		||||
	InitDaemon(appName)
 | 
			
		||||
	api := &tools.API{}
 | 
			
		||||
	api.Discovered(beego.BeeApp.Handlers.GetAllControllerInfo())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
@@ -133,42 +202,6 @@ func SetConfig(mongoUrl string, database string, natsUrl string, lokiUrl string,
 | 
			
		||||
	}()
 | 
			
		||||
	logs.CreateLogger("main")
 | 
			
		||||
	mongo.MONGOService.Init(models.GetModelsNames(), config.GetConfig()) // init the mongo service
 | 
			
		||||
	/*
 | 
			
		||||
		Here we will check if the resource model is already stored in the database
 | 
			
		||||
		If not we will store it
 | 
			
		||||
		Resource model is the model that will define the structure of the resources
 | 
			
		||||
	*/
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	for _, model := range []string{utils.DATA_RESOURCE.String(), utils.PROCESSING_RESOURCE.String(), utils.STORAGE_RESOURCE.String(), utils.DATACENTER_RESOURCE.String(), utils.WORKFLOW_RESOURCE.String()} {
 | 
			
		||||
		data, code, _ := accessor.Search(nil, model)
 | 
			
		||||
		if code == 404 || len(data) == 0 {
 | 
			
		||||
			m := map[string]resource_model.Model{}
 | 
			
		||||
			// TODO Specify the model for each resource
 | 
			
		||||
			// for now only processing is specified here (not an elegant way)
 | 
			
		||||
			if model == utils.PROCESSING_RESOURCE.String() {
 | 
			
		||||
				m["image"] = resource_model.Model{
 | 
			
		||||
					Type:     "string",
 | 
			
		||||
					ReadOnly: false,
 | 
			
		||||
				}
 | 
			
		||||
				m["command"] = resource_model.Model{
 | 
			
		||||
					Type:     "string",
 | 
			
		||||
					ReadOnly: false,
 | 
			
		||||
				}
 | 
			
		||||
				m["args"] = resource_model.Model{
 | 
			
		||||
					Type:     "string",
 | 
			
		||||
					ReadOnly: false,
 | 
			
		||||
				}
 | 
			
		||||
				m["execute"] = resource_model.Model{
 | 
			
		||||
					Type:     "map[int]map[string]string",
 | 
			
		||||
					ReadOnly: false,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			accessor.StoreOne(&resource_model.ResourceModel{
 | 
			
		||||
				ResourceType: model,
 | 
			
		||||
				Model:        m,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -198,6 +231,64 @@ func GetConfLoader() *onion.Onion {
 | 
			
		||||
	return config.GetConfLoader()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Request struct {
 | 
			
		||||
	collection LibDataEnum
 | 
			
		||||
	user       string
 | 
			
		||||
	peerID     string
 | 
			
		||||
	groups     []string
 | 
			
		||||
	caller     *tools.HTTPCaller
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRequest(collection LibDataEnum, user string, peerID string, groups []string, caller *tools.HTTPCaller) *Request {
 | 
			
		||||
	return &Request{collection: collection, user: user, peerID: peerID, groups: groups, caller: caller}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToScheduler(m interface{}) (n *workflow_execution.WorkflowSchedule) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	return m.(*workflow_execution.WorkflowSchedule)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Request) Schedule(wfID string, scheduler *workflow_execution.WorkflowSchedule) (*workflow_execution.WorkflowSchedule, error) {
 | 
			
		||||
	ws, _, _, err := scheduler.Schedules(wfID, &tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return ws, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Request) CheckBooking(wfID string, start string, end string, durationInS float64, cron string) bool {
 | 
			
		||||
	ok, _, _, _, _, err := workflow_execution.NewScheduler(start, end, durationInS, cron).GetBuyAndBook(wfID, &tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Request) PaymentTunnel(o *order.Order, scheduler *workflow_execution.WorkflowSchedule) error {
 | 
			
		||||
	/*return o.Pay(scheduler, &tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	})*/
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Search will search for the data in the database
 | 
			
		||||
* @param filters *dbs.Filters
 | 
			
		||||
@@ -206,18 +297,19 @@ func GetConfLoader() *onion.Onion {
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibDataShallow
 | 
			
		||||
 */
 | 
			
		||||
func Search(filters *dbs.Filters, word string, collection LibDataEnum, c ...*tools.HTTPCaller) (data LibDataShallow) {
 | 
			
		||||
func (r *Request) Search(filters *dbs.Filters, word string, isDraft bool) (data LibDataShallow) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in Search : "+fmt.Sprintf("%v", r)))
 | 
			
		||||
			data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	d, code, err := models.Model(collection.EnumIndex()).GetAccessor(caller).Search(filters, word)
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).Search(filters, word, isDraft)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -232,18 +324,19 @@ func Search(filters *dbs.Filters, word string, collection LibDataEnum, c ...*too
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibDataShallow
 | 
			
		||||
 */
 | 
			
		||||
func LoadAll(collection LibDataEnum, c ...*tools.HTTPCaller) (data LibDataShallow) {
 | 
			
		||||
func (r *Request) LoadAll(isDraft bool) (data LibDataShallow) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadAll : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibDataShallow{Data: nil, Code: 500, Err: "Panic recovered in LoadAll : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	d, code, err := models.Model(collection.EnumIndex()).GetAccessor(caller).LoadAll()
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).LoadAll(isDraft)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibDataShallow{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -259,18 +352,19 @@ func LoadAll(collection LibDataEnum, c ...*tools.HTTPCaller) (data LibDataShallo
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func LoadOne(collection LibDataEnum, id string, c ...*tools.HTTPCaller) (data LibData) {
 | 
			
		||||
func (r *Request) LoadOne(id string) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in LoadOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in LoadOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	d, code, err := models.Model(collection.EnumIndex()).GetAccessor(caller).LoadOne(id)
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -287,19 +381,20 @@ func LoadOne(collection LibDataEnum, id string, c ...*tools.HTTPCaller) (data Li
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func UpdateOne(collection LibDataEnum, set map[string]interface{}, id string, c ...*tools.HTTPCaller) (data LibData) {
 | 
			
		||||
func (r *Request) UpdateOne(set map[string]interface{}, id string) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in UpdateOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	model := models.Model(collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(caller).UpdateOne(model.Deserialize(set), id)
 | 
			
		||||
	model := models.Model(r.collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).UpdateOne(model.Deserialize(set, model), id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -315,18 +410,19 @@ func UpdateOne(collection LibDataEnum, set map[string]interface{}, id string, c
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func DeleteOne(collection LibDataEnum, id string, c ...*tools.HTTPCaller) (data LibData) {
 | 
			
		||||
func (r *Request) DeleteOne(id string) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in DeleteOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in DeleteOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	d, code, err := models.Model(collection.EnumIndex()).GetAccessor(caller).DeleteOne(id)
 | 
			
		||||
	d, code, err := models.Model(r.collection.EnumIndex()).GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).DeleteOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -342,19 +438,20 @@ func DeleteOne(collection LibDataEnum, id string, c ...*tools.HTTPCaller) (data
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func StoreOne(collection LibDataEnum, object map[string]interface{}, c ...*tools.HTTPCaller) (data LibData) {
 | 
			
		||||
func (r *Request) StoreOne(object map[string]interface{}) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in StoreOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in StoreOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	model := models.Model(collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(caller).StoreOne(model.Deserialize(object))
 | 
			
		||||
	model := models.Model(r.collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).StoreOne(model.Deserialize(object, model))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -370,19 +467,20 @@ func StoreOne(collection LibDataEnum, object map[string]interface{}, c ...*tools
 | 
			
		||||
* @param c ...*tools.HTTPCaller
 | 
			
		||||
* @return data LibData
 | 
			
		||||
 */
 | 
			
		||||
func CopyOne(collection LibDataEnum, object map[string]interface{}, c ...*tools.HTTPCaller) (data LibData) {
 | 
			
		||||
func (r *Request) CopyOne(object map[string]interface{}) (data LibData) {
 | 
			
		||||
	defer func() { // recover the panic
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tools.UncatchedError = append(tools.UncatchedError, errors.New("Panic recovered in CopyOne : "+fmt.Sprintf("%v", r)+" - "+string(debug.Stack())))
 | 
			
		||||
			data = LibData{Data: nil, Code: 500, Err: "Panic recovered in UpdateOne : " + fmt.Sprintf("%v", r) + " - " + string(debug.Stack())}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var caller *tools.HTTPCaller // define the caller
 | 
			
		||||
	if len(c) > 0 {
 | 
			
		||||
		caller = c[0]
 | 
			
		||||
	}
 | 
			
		||||
	model := models.Model(collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(caller).CopyOne(model.Deserialize(object))
 | 
			
		||||
	model := models.Model(r.collection.EnumIndex())
 | 
			
		||||
	d, code, err := model.GetAccessor(&tools.APIRequest{
 | 
			
		||||
		Caller:   r.caller,
 | 
			
		||||
		Username: r.user,
 | 
			
		||||
		PeerID:   r.peerID,
 | 
			
		||||
		Groups:   r.groups,
 | 
			
		||||
	}).CopyOne(model.Deserialize(object, model))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		data = LibData{Data: d, Code: code, Err: err.Error()}
 | 
			
		||||
		return
 | 
			
		||||
@@ -393,74 +491,109 @@ func CopyOne(collection LibDataEnum, object map[string]interface{}, c ...*tools.
 | 
			
		||||
 | 
			
		||||
// ================ CAST ========================= //
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToDataResource() *data.DataResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.DATA_RESOURCE.String() {
 | 
			
		||||
		return l.Data.(*data.DataResource)
 | 
			
		||||
func (l *LibData) ToDataResource() *resources.DataResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.DATA_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.DataResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToDatacenterResource() *datacenter.DatacenterResource {
 | 
			
		||||
	if l.Data != nil && l.Data.GetAccessor(nil).GetType() == utils.DATACENTER_RESOURCE.String() {
 | 
			
		||||
		return l.Data.(*datacenter.DatacenterResource)
 | 
			
		||||
func (l *LibData) ToComputeResource() *resources.ComputeResource {
 | 
			
		||||
	if l.Data != nil && l.Data.GetAccessor(nil).GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.ComputeResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToStorageResource() *storage.StorageResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.STORAGE_RESOURCE.String() {
 | 
			
		||||
		return l.Data.(*storage.StorageResource)
 | 
			
		||||
func (l *LibData) ToStorageResource() *resources.StorageResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.STORAGE_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.StorageResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToProcessingResource() *processing.ProcessingResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.PROCESSING_RESOURCE.String() {
 | 
			
		||||
		return l.Data.(*processing.ProcessingResource)
 | 
			
		||||
func (l *LibData) ToProcessingResource() *resources.ProcessingResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.PROCESSING_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.ProcessingResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToWorkflowResource() *w.WorkflowResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.WORKFLOW_RESOURCE.String() {
 | 
			
		||||
		return l.Data.(*w.WorkflowResource)
 | 
			
		||||
func (l *LibData) ToWorkflowResource() *resources.WorkflowResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_RESOURCE {
 | 
			
		||||
		return l.Data.(*resources.WorkflowResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToPeer() *peer.Peer {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.PEER.String() {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.PEER {
 | 
			
		||||
		return l.Data.(*peer.Peer)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToWorkflow() *w2.Workflow {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.WORKFLOW.String() {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW {
 | 
			
		||||
		return l.Data.(*w2.Workflow)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (l *LibData) ToWorkspace() *workspace.Workspace {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.WORKSPACE.String() {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKSPACE {
 | 
			
		||||
		return l.Data.(*workspace.Workspace)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToSharedWorkspace() *shared_workspace.SharedWorkspace {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.SHARED_WORKSPACE.String() {
 | 
			
		||||
		return l.Data.(*shared_workspace.SharedWorkspace)
 | 
			
		||||
func (l *LibData) ToCollaborativeArea() *collaborative_area.CollaborativeArea {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA {
 | 
			
		||||
		return l.Data.(*collaborative_area.CollaborativeArea)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToRule() *rule.Rule {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.SHARED_WORKSPACE.String() {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.COLLABORATIVE_AREA {
 | 
			
		||||
		return l.Data.(*rule.Rule)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToWorkflowExecution() *workflow_execution.WorkflowExecution {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == utils.WORKFLOW_EXECUTION.String() {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.WORKFLOW_EXECUTION {
 | 
			
		||||
		return l.Data.(*workflow_execution.WorkflowExecution)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToOrder() *order.Order {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.ORDER {
 | 
			
		||||
		return l.Data.(*order.Order)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToLiveDatacenter() *live.LiveDatacenter {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.LIVE_DATACENTER {
 | 
			
		||||
		return l.Data.(*live.LiveDatacenter)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToLiveStorage() *live.LiveStorage {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.LIVE_STORAGE {
 | 
			
		||||
		return l.Data.(*live.LiveStorage)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToBookings() *booking.Booking {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.BOOKING {
 | 
			
		||||
		return l.Data.(*booking.Booking)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LibData) ToPurchasedResource() *purchase_resource.PurchaseResource {
 | 
			
		||||
	if l.Data.GetAccessor(nil).GetType() == tools.PURCHASE_RESOURCE {
 | 
			
		||||
		return l.Data.(*purchase_resource.PurchaseResource)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								go.mod
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										19
									
								
								go.mod
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -3,18 +3,20 @@ module cloud.o-forge.io/core/oc-lib
 | 
			
		||||
go 1.22.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/beego/beego/v2 v2.3.1
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.22.0
 | 
			
		||||
	github.com/google/uuid v1.6.0
 | 
			
		||||
	github.com/goraz/onion v0.1.3
 | 
			
		||||
	github.com/nats-io/nats.go v1.37.0
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1
 | 
			
		||||
	github.com/rs/zerolog v1.33.0
 | 
			
		||||
	github.com/stretchr/testify v1.9.0
 | 
			
		||||
	github.com/stretchr/testify v1.10.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/nats-io/nkeys v0.4.7 // indirect
 | 
			
		||||
	github.com/nats-io/nuid v1.0.1 // indirect
 | 
			
		||||
	github.com/stretchr/objx v0.5.2 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
@@ -25,16 +27,27 @@ require (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/beorn7/perks v1.0.1 // indirect
 | 
			
		||||
	github.com/biter777/countries v1.7.5
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.4 // indirect
 | 
			
		||||
	github.com/go-playground/locales v0.14.1 // indirect
 | 
			
		||||
	github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
			
		||||
	github.com/hashicorp/golang-lru v0.5.4 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.17.9 // indirect
 | 
			
		||||
	github.com/kr/text v0.1.0 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
			
		||||
	github.com/montanaflynn/stats v0.7.1 // indirect
 | 
			
		||||
	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_golang v1.19.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.5.0 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.48.0 // indirect
 | 
			
		||||
	github.com/prometheus/procfs v0.12.0 // indirect
 | 
			
		||||
	github.com/robfig/cron v1.2.0
 | 
			
		||||
	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 | 
			
		||||
	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
			
		||||
	github.com/xdg-go/scram v1.1.2 // indirect
 | 
			
		||||
	github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
			
		||||
@@ -43,6 +56,6 @@ require (
 | 
			
		||||
	golang.org/x/net v0.27.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.7.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.16.0 // indirect
 | 
			
		||||
	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.34.2 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								go.sum
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										42
									
								
								go.sum
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,10 +1,20 @@
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/beego/beego/v2 v2.3.1 h1:7MUKMpJYzOXtCUsTEoXOxsDV/UcHw6CPbaWMlthVNsc=
 | 
			
		||||
github.com/beego/beego/v2 v2.3.1/go.mod h1:5cqHsOHJIxkq44tBpRvtDe59GuVRVv/9/tyVDxd5ce4=
 | 
			
		||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
			
		||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
			
		||||
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
 | 
			
		||||
github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
			
		||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
 | 
			
		||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 | 
			
		||||
github.com/etcd-io/etcd v3.3.17+incompatible/go.mod h1:cdZ77EstHBwVtD6iTgzgvogwcjo9m4iOqoijouPJ4bs=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
 | 
			
		||||
@@ -29,12 +39,16 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/goraz/onion v0.1.3 h1:KhyvbDA2b70gcz/d5izfwTiOH8SmrvV43AsVzpng3n0=
 | 
			
		||||
github.com/goraz/onion v0.1.3/go.mod h1:XEmz1XoBz+wxTgWB8NwuvRm4RAu3vKxvrmYtzK+XCuQ=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 | 
			
		||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 | 
			
		||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
			
		||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 | 
			
		||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
			
		||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
 | 
			
		||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 | 
			
		||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
			
		||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
@@ -48,6 +62,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
@@ -60,27 +76,43 @@ github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
 | 
			
		||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
 | 
			
		||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
 | 
			
		||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 | 
			
		||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 | 
			
		||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
			
		||||
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
 | 
			
		||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
 | 
			
		||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
 | 
			
		||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 | 
			
		||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
 | 
			
		||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
 | 
			
		||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
 | 
			
		||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 | 
			
		||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 | 
			
		||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
 | 
			
		||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 | 
			
		||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 | 
			
		||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
 | 
			
		||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 | 
			
		||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
 | 
			
		||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 | 
			
		||||
github.com/skarademir/naturalsort v0.0.0-20150715044055-69a5d87bef62/go.mod h1:oIdVclZaltY1Nf7OQUkg1/2jImBJ+ZfKZuDIRSwk3p0=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
 | 
			
		||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 | 
			
		||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 | 
			
		||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
			
		||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 | 
			
		||||
@@ -134,9 +166,11 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
 | 
			
		||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										202
									
								
								models/bill/bill.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								models/bill/bill.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
			
		||||
package bill
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Booking is a struct that represents a booking
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type Bill struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	OrderID   string                `json:"order_id" bson:"order_id" validate:"required"`
 | 
			
		||||
	Status    enum.CompletionStatus `json:"status" bson:"status" default:"0"`
 | 
			
		||||
	SubOrders map[string]*PeerOrder `json:"sub_orders" bson:"sub_orders"`
 | 
			
		||||
	Total     float64               `json:"total" bson:"total" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenerateBill(order *order.Order, request *tools.APIRequest) (*Bill, error) {
 | 
			
		||||
	// hhmmm : should get... the loop.
 | 
			
		||||
	return &Bill{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{
 | 
			
		||||
			Name:    "bill_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
 | 
			
		||||
			IsDraft: false,
 | 
			
		||||
		},
 | 
			
		||||
		OrderID: order.UUID,
 | 
			
		||||
		Status:  enum.PENDING,
 | 
			
		||||
		// SubOrders: peerOrders,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DraftFirstBill(order *order.Order, request *tools.APIRequest) (*Bill, error) {
 | 
			
		||||
	peers := map[string][]*PeerItemOrder{}
 | 
			
		||||
	for _, p := range order.Purchases {
 | 
			
		||||
		// TODO : if once
 | 
			
		||||
		if _, ok := peers[p.DestPeerID]; !ok {
 | 
			
		||||
			peers[p.DestPeerID] = []*PeerItemOrder{}
 | 
			
		||||
		}
 | 
			
		||||
		peers[p.DestPeerID] = append(peers[p.DestPeerID], &PeerItemOrder{
 | 
			
		||||
			Purchase: p,
 | 
			
		||||
			Item:     p.PricedItem,
 | 
			
		||||
			Quantity: 1,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for _, b := range order.Bookings {
 | 
			
		||||
		// TODO : if once
 | 
			
		||||
		isPurchased := false
 | 
			
		||||
		for _, p := range order.Purchases {
 | 
			
		||||
			if p.ResourceID == b.ResourceID {
 | 
			
		||||
				isPurchased = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if isPurchased {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := peers[b.DestPeerID]; !ok {
 | 
			
		||||
			peers[b.DestPeerID] = []*PeerItemOrder{}
 | 
			
		||||
		}
 | 
			
		||||
		peers[b.DestPeerID] = append(peers[b.DestPeerID], &PeerItemOrder{
 | 
			
		||||
			Item: b.PricedItem,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	peerOrders := map[string]*PeerOrder{}
 | 
			
		||||
	for peerID, items := range peers {
 | 
			
		||||
		pr, _, err := peer.NewAccessor(request).LoadOne(peerID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		peerOrders[peerID] = &PeerOrder{
 | 
			
		||||
			PeerID:         peerID,
 | 
			
		||||
			BillingAddress: pr.(*peer.Peer).WalletAddress,
 | 
			
		||||
			Items:          items,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	bill := &Bill{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{
 | 
			
		||||
			Name:    "bill_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
 | 
			
		||||
			IsDraft: true,
 | 
			
		||||
		},
 | 
			
		||||
		OrderID:   order.UUID,
 | 
			
		||||
		Status:    enum.PENDING,
 | 
			
		||||
		SubOrders: peerOrders,
 | 
			
		||||
	}
 | 
			
		||||
	return bill.SumUpBill(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Bill) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Bill) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Bill) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if !r.IsDraft && r.Status != set.(*Bill).Status {
 | 
			
		||||
		return true, &Bill{Status: set.(*Bill).Status} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return r.IsDraft, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Bill) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft order can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Bill) SumUpBill(request *tools.APIRequest) (*Bill, error) {
 | 
			
		||||
	for _, b := range d.SubOrders {
 | 
			
		||||
		err := b.SumUpBill(request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return d, err
 | 
			
		||||
		}
 | 
			
		||||
		d.Total += b.Total
 | 
			
		||||
	}
 | 
			
		||||
	return d, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PeerOrder struct {
 | 
			
		||||
	Error          string                `json:"error,omitempty" bson:"error,omitempty"`
 | 
			
		||||
	PeerID         string                `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
 | 
			
		||||
	Status         enum.CompletionStatus `json:"status" bson:"status" default:"0"`
 | 
			
		||||
	BillingAddress string                `json:"billing_address,omitempty" bson:"billing_address,omitempty"`
 | 
			
		||||
	Items          []*PeerItemOrder      `json:"items,omitempty" bson:"items,omitempty"`
 | 
			
		||||
	Total          float64               `json:"total,omitempty" bson:"total,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PeerOrder) Pay(request *tools.APIRequest, response chan *PeerOrder, wg *sync.WaitGroup) {
 | 
			
		||||
	d.Status = enum.PENDING
 | 
			
		||||
	go func() {
 | 
			
		||||
		// DO SOMETHING TO PAY ON BLOCKCHAIN OR WHATEVER ON RETURN UPDATE STATUS
 | 
			
		||||
		d.Status = enum.PAID // TO REMOVE LATER IT'S A MOCK
 | 
			
		||||
		if d.Status == enum.PAID {
 | 
			
		||||
			for _, b := range d.Items {
 | 
			
		||||
				if !b.Item.IsPurchasable() {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				accessor := purchase_resource.NewAccessor(request)
 | 
			
		||||
				accessor.StoreOne(&purchase_resource.PurchaseResource{
 | 
			
		||||
					ResourceID:   b.Item.GetID(),
 | 
			
		||||
					ResourceType: b.Item.GetType(),
 | 
			
		||||
					EndDate:      b.Item.GetLocationEnd(),
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if d.Status != enum.PENDING {
 | 
			
		||||
			response <- d
 | 
			
		||||
		}
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PeerOrder) SumUpBill(request *tools.APIRequest) error {
 | 
			
		||||
	for _, b := range d.Items {
 | 
			
		||||
		tot, err := b.GetPrice(request) // missing something
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		d.Total += tot
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PeerItemOrder struct {
 | 
			
		||||
	Quantity int                                 `json:"quantity,omitempty" bson:"quantity,omitempty"`
 | 
			
		||||
	Purchase *purchase_resource.PurchaseResource `json:"purchase,omitempty" bson:"purchase,omitempty"`
 | 
			
		||||
	Item     pricing.PricedItemITF               `json:"item,omitempty" bson:"item,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PeerItemOrder) GetPrice(request *tools.APIRequest) (float64, error) {
 | 
			
		||||
	accessor := purchase_resource.NewAccessor(request)
 | 
			
		||||
	search, code, _ := accessor.Search(&dbs.Filters{
 | 
			
		||||
		And: map[string][]dbs.Filter{
 | 
			
		||||
			"resource_id": {{Operator: dbs.EQUAL.String(), Value: d.Item.GetID()}},
 | 
			
		||||
		},
 | 
			
		||||
	}, "", d.Purchase.IsDraft)
 | 
			
		||||
	if code == 200 && len(search) > 0 {
 | 
			
		||||
		for _, s := range search {
 | 
			
		||||
			if s.(*purchase_resource.PurchaseResource).EndDate == nil || time.Now().UTC().After(*s.(*purchase_resource.PurchaseResource).EndDate) {
 | 
			
		||||
				return 0, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	p, err := d.Item.GetPrice()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return p * float64(d.Quantity), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WTF HOW TO SELECT THE RIGHT PRICE ???
 | 
			
		||||
// SHOULD SET A BUYING STATUS WHEN PAYMENT IS VALIDATED
 | 
			
		||||
							
								
								
									
										63
									
								
								models/bill/bill_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								models/bill/bill_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
package bill
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type billMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the billMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *billMongoAccessor {
 | 
			
		||||
	return &billMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.LIVE_DATACENTER.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.LIVE_DATACENTER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *billMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	// should verify if a source is existing...
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &Bill{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*Bill), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*Bill), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Bill](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Bill](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Bill](filters, search, (&Bill{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *billMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								models/billing_process.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								models/billing_process.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# Billing process 
 | 
			
		||||
Scheduler process a drafted order + a first bill corresponding to every once buying. 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
package booking
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson/primitive"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -16,74 +16,129 @@ import (
 | 
			
		||||
* Booking is a struct that represents a booking
 | 
			
		||||
 */
 | 
			
		||||
type Booking struct {
 | 
			
		||||
	workflow_execution.WorkflowExecution        // WorkflowExecution contains the workflow execution data
 | 
			
		||||
	DatacenterResourceID                 string `json:"datacenter_resource_id,omitempty" bson:"datacenter_resource_id,omitempty" validate:"required"` // DatacenterResourceID is the ID of the datacenter resource specified in the booking
 | 
			
		||||
	utils.AbstractObject                       // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	PricedItem           pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty"` // We need to add the validate:"required" tag once the pricing feature is implemented, removed to avoid handling the error
 | 
			
		||||
 | 
			
		||||
	ResumeMetrics    map[string]map[string]models.MetricResume `json:"resume_metrics,omitempty" bson:"resume_metrics,omitempty"` 
 | 
			
		||||
	ExecutionMetrics map[string][]models.MetricsSnapshot       `json:"metrics,omitempty" bson:"metrics,omitempty"`
 | 
			
		||||
 | 
			
		||||
	ExecutionsID      string             `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
 | 
			
		||||
	DestPeerID        string             `json:"dest_peer_id,omitempty"`                                                     // DestPeerID is the ID of the destination peer
 | 
			
		||||
	WorkflowID        string             `json:"workflow_id,omitempty" bson:"workflow_id,omitempty"`                         // WorkflowID is the ID of the workflow
 | 
			
		||||
	ExecutionID       string             `json:"execution_id,omitempty" bson:"execution_id,omitempty" validate:"required"`
 | 
			
		||||
	State             enum.BookingStatus `json:"state,omitempty" bson:"state,omitempty" validate:"required"`                             // State is the state of the booking
 | 
			
		||||
	ExpectedStartDate time.Time          `json:"expected_start_date,omitempty" bson:"expected_start_date,omitempty" validate:"required"` // ExpectedStartDate is the expected start date of the booking
 | 
			
		||||
	ExpectedEndDate   *time.Time         `json:"expected_end_date,omitempty" bson:"expected_end_date,omitempty" validate:"required"`     // ExpectedEndDate is the expected end date of the booking
 | 
			
		||||
 | 
			
		||||
	RealStartDate *time.Time `json:"real_start_date,omitempty" bson:"real_start_date,omitempty"` // RealStartDate is the real start date of the booking
 | 
			
		||||
	RealEndDate   *time.Time `json:"real_end_date,omitempty" bson:"real_end_date,omitempty"`     // RealEndDate is the real end date of the booking
 | 
			
		||||
 | 
			
		||||
	ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"` // ResourceType is the type of the resource
 | 
			
		||||
	ResourceID   string         `json:"resource_id,omitempty" bson:"resource_id,omitempty" validate:"required"`     // could be a Compute or a Storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBooking checks if a booking is possible on a specific datacenter resource
 | 
			
		||||
func (wfa *Booking) CheckBooking(id string, start time.Time, end *time.Time) (bool, error) {
 | 
			
		||||
func (b *Booking) CalcDeltaOfExecution() map[string]map[string]models.MetricResume {
 | 
			
		||||
	m := map[string]map[string]models.MetricResume{}
 | 
			
		||||
	for instance, snapshot := range b.ExecutionMetrics {
 | 
			
		||||
		m[instance] = map[string]models.MetricResume{}
 | 
			
		||||
		for _, metric := range snapshot {
 | 
			
		||||
			for _, mm := range metric.Metrics {
 | 
			
		||||
				if resume, ok := m[instance][mm.Name]; !ok {
 | 
			
		||||
					m[instance][mm.Name] = models.MetricResume{
 | 
			
		||||
						Delta:     0,
 | 
			
		||||
						LastValue: mm.Value,
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					delta := resume.LastValue - mm.Value
 | 
			
		||||
					if delta == 0 {
 | 
			
		||||
						resume.Delta = delta
 | 
			
		||||
					} else {
 | 
			
		||||
						resume.Delta = (resume.Delta + delta) / 2
 | 
			
		||||
					}
 | 
			
		||||
					resume.LastValue = mm.Value
 | 
			
		||||
					m[instance][mm.Name] = resume
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBooking checks if a booking is possible on a specific compute resource
 | 
			
		||||
func (wfa *Booking) Check(id string, start time.Time, end *time.Time, parrallelAllowed int) (bool, error) {
 | 
			
		||||
	// check if
 | 
			
		||||
	if end == nil {
 | 
			
		||||
		// if no end... then Book like a savage
 | 
			
		||||
		return true, nil
 | 
			
		||||
		e := start.Add(time.Hour)
 | 
			
		||||
		end = &e
 | 
			
		||||
	}
 | 
			
		||||
	e := *end
 | 
			
		||||
	accessor := wfa.GetAccessor(nil)
 | 
			
		||||
	accessor := NewAccessor(nil)
 | 
			
		||||
	res, code, err := accessor.Search(&dbs.Filters{
 | 
			
		||||
		And: map[string][]dbs.Filter{ // check if there is a booking on the same datacenter resource by filtering on the datacenter_resource_id, the state and the execution date
 | 
			
		||||
			"datacenter_resource_id":  {{Operator: dbs.EQUAL.String(), Value: id}},
 | 
			
		||||
			"workflowexecution.state": {{Operator: dbs.EQUAL.String(), Value: workflow_execution.SCHEDULED.EnumIndex()}},
 | 
			
		||||
			"workflowexecution.execution_date": {
 | 
			
		||||
				{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(e)},
 | 
			
		||||
		And: map[string][]dbs.Filter{ // check if there is a booking on the same compute resource by filtering on the compute_resource_id, the state and the execution date
 | 
			
		||||
			"resource_id": {{Operator: dbs.EQUAL.String(), Value: id}},
 | 
			
		||||
			"state":       {{Operator: dbs.EQUAL.String(), Value: enum.DRAFT.EnumIndex()}},
 | 
			
		||||
			"expected_start_date": {
 | 
			
		||||
				{Operator: dbs.LTE.String(), Value: primitive.NewDateTimeFromTime(*end)},
 | 
			
		||||
				{Operator: dbs.GTE.String(), Value: primitive.NewDateTimeFromTime(start)},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, "")
 | 
			
		||||
	}, "", wfa.IsDraft)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return len(res) == 0, nil
 | 
			
		||||
	return len(res) <= parrallelAllowed, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tool to convert the argo status to a state
 | 
			
		||||
func (wfa *Booking) ArgoStatusToState(status string) *Booking {
 | 
			
		||||
	wfa.WorkflowExecution.ArgoStatusToState(status)
 | 
			
		||||
	return wfa
 | 
			
		||||
func (d *Booking) GetDelayForLaunch() time.Duration {
 | 
			
		||||
	return d.RealStartDate.Sub(d.ExpectedStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Booking) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
func (d *Booking) GetDelayForFinishing() time.Duration {
 | 
			
		||||
	if d.ExpectedEndDate == nil {
 | 
			
		||||
		return time.Duration(0)
 | 
			
		||||
	}
 | 
			
		||||
	return d.RealEndDate.Sub(d.ExpectedStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Booking) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
func (d *Booking) GetUsualDuration() time.Duration {
 | 
			
		||||
	return d.ExpectedEndDate.Sub(d.ExpectedStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetName() string {
 | 
			
		||||
	return d.UUID + "_" + d.ExecDate.String()
 | 
			
		||||
func (d *Booking) GetRealDuration() time.Duration {
 | 
			
		||||
	if d.RealEndDate == nil || d.RealStartDate == nil {
 | 
			
		||||
		return time.Duration(0)
 | 
			
		||||
	}
 | 
			
		||||
	return d.RealEndDate.Sub(*d.RealStartDate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Booking) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                    // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.BOOKING, caller) // Initialize the accessor with the BOOKING model type
 | 
			
		||||
	return data
 | 
			
		||||
func (d *Booking) GetDelayOnDuration() time.Duration {
 | 
			
		||||
	return d.GetRealDuration() - d.GetUsualDuration()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Booking) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
func (d *Booking) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Booking) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
func (d *Booking) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
 | 
			
		||||
func (r *Booking) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Booking) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if !r.IsDraft && r.State != set.(*Booking).State || r.RealStartDate != set.(*Booking).RealStartDate || r.RealEndDate != set.(*Booking).RealEndDate {
 | 
			
		||||
		return true, &Booking{
 | 
			
		||||
			State:         set.(*Booking).State,
 | 
			
		||||
			RealStartDate: set.(*Booking).RealStartDate,
 | 
			
		||||
			RealEndDate:   set.(*Booking).RealEndDate,
 | 
			
		||||
		} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	// TODO : HERE WE CAN HANDLE THE CASE WHERE THE BOOKING IS DELAYED OR EXCEEDING OR ending sooner
 | 
			
		||||
	return r.IsDraft, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Booking) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,88 +1,92 @@
 | 
			
		||||
package booking
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type bookingMongoAccessor struct {
 | 
			
		||||
type BookingMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the bookingMongoAccessor
 | 
			
		||||
func New() *bookingMongoAccessor {
 | 
			
		||||
	return &bookingMongoAccessor{}
 | 
			
		||||
// New creates a new instance of the BookingMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *BookingMongoAccessor {
 | 
			
		||||
	return &BookingMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.BOOKING.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.BOOKING,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (wfa *bookingMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericDeleteOne(id, wfa)
 | 
			
		||||
func (a *BookingMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *bookingMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericUpdateOne(set, id, wfa, &Booking{})
 | 
			
		||||
func (a *BookingMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	if set.(*Booking).State == 0 {
 | 
			
		||||
		return nil, 400, errors.New("state is required")
 | 
			
		||||
	}
 | 
			
		||||
	realSet := &Booking{State: set.(*Booking).State}
 | 
			
		||||
	return utils.GenericUpdateOne(realSet, id, a, &Booking{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *bookingMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
func (a *BookingMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *bookingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
func (a *BookingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *bookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var workflow Booking
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
func (a *BookingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Booking](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		now = now.Add(time.Second * -60)
 | 
			
		||||
		if d.(*Booking).State == enum.DRAFT && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			return utils.GenericDeleteOne(d.GetID(), a)
 | 
			
		||||
		}
 | 
			
		||||
	res_mongo.Decode(&workflow)
 | 
			
		||||
	return &workflow, 200, nil
 | 
			
		||||
		if (d.(*Booking).ExpectedEndDate) == nil {
 | 
			
		||||
			d.(*Booking).State = enum.FORGOTTEN
 | 
			
		||||
			utils.GenericRawUpdateOne(d, id, a)
 | 
			
		||||
		} else if d.(*Booking).State == enum.SCHEDULED && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			d.(*Booking).State = enum.DELAYED
 | 
			
		||||
			utils.GenericRawUpdateOne(d, id, a)
 | 
			
		||||
		}
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa bookingMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []Booking
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r.AbstractObject) // Warning only AbstractObject is returned
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
func (a *BookingMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Booking](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Search is a function that searches for a booking in the database
 | 
			
		||||
func (wfa *bookingMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
 | 
			
		||||
				"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
func (a *BookingMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Booking](filters, search, (&Booking{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *BookingMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		now = now.Add(time.Second * -60)
 | 
			
		||||
		if d.(*Booking).State == enum.DRAFT && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			utils.GenericDeleteOne(d.GetID(), a)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if d.(*Booking).State == enum.SCHEDULED && now.UTC().After(d.(*Booking).ExpectedStartDate) {
 | 
			
		||||
			d.(*Booking).State = enum.DELAYED
 | 
			
		||||
			utils.GenericRawUpdateOne(d, d.GetID(), a)
 | 
			
		||||
		}
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []Booking
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								models/booking/tests/booking_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								models/booking/tests/booking_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
package booking_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBooking_GetDurations(t *testing.T) {
 | 
			
		||||
	start := time.Now().Add(-2 * time.Hour)
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	realStart := start.Add(30 * time.Minute)
 | 
			
		||||
	realEnd := realStart.Add(90 * time.Minute)
 | 
			
		||||
 | 
			
		||||
	b := &booking.Booking{
 | 
			
		||||
		ExpectedStartDate: start,
 | 
			
		||||
		ExpectedEndDate:   &end,
 | 
			
		||||
		RealStartDate:     &realStart,
 | 
			
		||||
		RealEndDate:       &realEnd,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, 30*time.Minute, b.GetDelayForLaunch())
 | 
			
		||||
	assert.Equal(t, 90*time.Minute, b.GetRealDuration())
 | 
			
		||||
	assert.Equal(t, end.Sub(start), b.GetUsualDuration())
 | 
			
		||||
	assert.Equal(t, b.GetRealDuration()-b.GetUsualDuration(), b.GetDelayOnDuration())
 | 
			
		||||
	assert.Equal(t, realEnd.Sub(start), b.GetDelayForFinishing())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_GetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	b := &booking.Booking{}
 | 
			
		||||
	accessor := b.GetAccessor(req)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
	assert.Equal(t, tools.BOOKING, accessor.(*booking.BookingMongoAccessor).Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_VerifyAuth(t *testing.T) {
 | 
			
		||||
	assert.True(t, (&booking.Booking{}).VerifyAuth(nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_StoreDraftDefault(t *testing.T) {
 | 
			
		||||
	b := &booking.Booking{}
 | 
			
		||||
	b.StoreDraftDefault()
 | 
			
		||||
	assert.False(t, b.IsDraft)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_CanUpdate(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	b := &booking.Booking{
 | 
			
		||||
		State:          enum.SCHEDULED,
 | 
			
		||||
		AbstractObject: utils.AbstractObject{IsDraft: false},
 | 
			
		||||
		RealStartDate:  &now,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set := &booking.Booking{
 | 
			
		||||
		State:         enum.DELAYED,
 | 
			
		||||
		RealStartDate: &now,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ok, result := b.CanUpdate(set)
 | 
			
		||||
	assert.True(t, ok)
 | 
			
		||||
	assert.Equal(t, enum.DELAYED, result.(*booking.Booking).State)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBooking_CanDelete(t *testing.T) {
 | 
			
		||||
	b := &booking.Booking{AbstractObject: utils.AbstractObject{IsDraft: true}}
 | 
			
		||||
	assert.True(t, b.CanDelete())
 | 
			
		||||
 | 
			
		||||
	b.IsDraft = false
 | 
			
		||||
	assert.False(t, b.CanDelete())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	accessor := booking.NewAccessor(req)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
	assert.Equal(t, tools.BOOKING, accessor.Type)
 | 
			
		||||
	assert.Equal(t, req, accessor.Request)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								models/collaborative_area/collaborative_area.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								models/collaborative_area/collaborative_area.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
package collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"slices"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/config"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CollaborativeAreaRule struct {
 | 
			
		||||
	ShareMode   string    `json:"share_mode,omitempty" bson:"share_mode,omitempty"`     // Share is the share of the rule
 | 
			
		||||
	CreatedAt   time.Time `json:"created_at,omitempty" bson:"created_at,omitempty"`     // CreatedAt is the time the rule was created
 | 
			
		||||
	Creator     string    `json:"creator,omitempty" bson:"creator,omitempty"`           // Creator is the creator of the rule
 | 
			
		||||
	ExploitedBy string    `json:"exploited_by,omitempty" bson:"exploited_by,omitempty"` // ExploitedBy is the exploited by of the rule
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SharedWorkspace is a struct that represents a shared workspace
 | 
			
		||||
// WARNING : it got a shallow object version, this one is the full version
 | 
			
		||||
// full version is the one used by API
 | 
			
		||||
// other one is a shallow version used by the Lib for import cycle problem purposes
 | 
			
		||||
type CollaborativeArea struct {
 | 
			
		||||
	utils.AbstractObject                         // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	IsSent                bool                   `json:"is_sent" bson:"-"`                                                       // IsSent is a flag that indicates if the workspace is sent
 | 
			
		||||
	Version               string                 `json:"version,omitempty" bson:"version,omitempty"`                             // Version is the version of the workspace
 | 
			
		||||
	Description           string                 `json:"description,omitempty" bson:"description,omitempty" validate:"required"` // Description is the description of the workspace
 | 
			
		||||
	CollaborativeAreaRule *CollaborativeAreaRule `json:"collaborative_area,omitempty" bson:"collaborative_area,omitempty"`       // CollaborativeArea is the collaborative area of the workspace
 | 
			
		||||
	Attributes            map[string]interface{} `json:"attributes,omitempty" bson:"attributes,omitempty"`                       // Attributes is the attributes of the workspace (TODO)
 | 
			
		||||
	Workspaces            []string               `json:"workspaces" bson:"workspaces"`                                           // Workspaces is the workspaces of the workspace
 | 
			
		||||
	Workflows             []string               `json:"workflows" bson:"workflows"`                                             // Workflows is the workflows of the workspace
 | 
			
		||||
	AllowedPeersGroup     map[string][]string    `json:"allowed_peers_group" bson:"allowed_peers_group"`                         // AllowedPeersGroup is the group of allowed peers
 | 
			
		||||
	Rules                 []string               `json:"rules" bson:"rules,omitempty"`                                           // Rules is the rules of the workspace
 | 
			
		||||
 | 
			
		||||
	SharedRules      []*rule.Rule           `json:"shared_rules,omitempty" bson:"-"`      // SharedRules is the shared rules of the workspace
 | 
			
		||||
	SharedWorkspaces []*workspace.Workspace `json:"shared_workspaces,omitempty" bson:"-"` // SharedWorkspaces is the shared workspaces of the workspace
 | 
			
		||||
	SharedWorkflows  []*w.Workflow          `json:"shared_workflows,omitempty" bson:"-"`  // SharedWorkflows is the shared workflows of the workspace
 | 
			
		||||
	SharedPeers      []*peer.Peer           `json:"shared_peers,omitempty" bson:"-"`      // SharedPeers is the shared peers of the workspace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *CollaborativeArea) Clear(peerID string) {
 | 
			
		||||
	if ao.AllowedPeersGroup == nil {
 | 
			
		||||
		ao.AllowedPeersGroup = map[string][]string{}
 | 
			
		||||
	}
 | 
			
		||||
	ao.CreatorID = peerID
 | 
			
		||||
	if config.GetConfig().Whitelist {
 | 
			
		||||
		ao.AllowedPeersGroup[peerID] = []string{"*"}
 | 
			
		||||
	} else {
 | 
			
		||||
		ao.AllowedPeersGroup[peerID] = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	// then reset the shared fields
 | 
			
		||||
	if ao.Workspaces == nil {
 | 
			
		||||
		ao.Workspaces = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if ao.Workflows == nil {
 | 
			
		||||
		ao.Workflows = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if ao.Rules == nil {
 | 
			
		||||
		ao.Rules = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if ao.CollaborativeAreaRule == nil {
 | 
			
		||||
		ao.CollaborativeAreaRule = &CollaborativeAreaRule{
 | 
			
		||||
			ShareMode:   "private",
 | 
			
		||||
			ExploitedBy: "collaborators only",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ao.CollaborativeAreaRule.CreatedAt = time.Now().UTC()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *CollaborativeArea) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	if (ao.AllowedPeersGroup != nil || config.GetConfig().Whitelist) && request != nil {
 | 
			
		||||
		if grps, ok := ao.AllowedPeersGroup[request.PeerID]; ok || config.GetConfig().Whitelist {
 | 
			
		||||
			if slices.Contains(grps, "*") || (!ok && config.GetConfig().Whitelist) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
			for _, grp := range grps {
 | 
			
		||||
				if slices.Contains(request.Groups, grp) {
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ao.AbstractObject.VerifyAuth(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *CollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *CollaborativeArea) Trim() *CollaborativeArea {
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *CollaborativeArea) StoreDraftDefault() {
 | 
			
		||||
	d.AllowedPeersGroup = map[string][]string{
 | 
			
		||||
		d.CreatorID: []string{"*"},
 | 
			
		||||
	}
 | 
			
		||||
	d.IsDraft = false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								models/collaborative_area/collaborative_area_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								models/collaborative_area/collaborative_area_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
package collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SharedWorkspace is a struct that represents a collaborative area
 | 
			
		||||
type collaborativeAreaMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
 | 
			
		||||
	workspaceAccessor utils.Accessor
 | 
			
		||||
	workflowAccessor  utils.Accessor
 | 
			
		||||
	peerAccessor      utils.Accessor
 | 
			
		||||
	ruleAccessor      utils.Accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *collaborativeAreaMongoAccessor {
 | 
			
		||||
	return &collaborativeAreaMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.COLLABORATIVE_AREA,
 | 
			
		||||
		},
 | 
			
		||||
		workspaceAccessor: (&workspace.Workspace{}).GetAccessor(request),
 | 
			
		||||
		workflowAccessor:  (&w.Workflow{}).GetAccessor(request),
 | 
			
		||||
		peerAccessor:      (&peer.Peer{}).GetAccessor(request),
 | 
			
		||||
		ruleAccessor:      (&rule.Rule{}).GetAccessor(request),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteOne deletes a collaborative area from the database, given its ID, it automatically share to peers if the workspace is shared
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set, code, err := a.LoadOne(id)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	a.deleteToPeer(set.(*CollaborativeArea))
 | 
			
		||||
	a.sharedWorkflow(&CollaborativeArea{}, id)  // create all shared workflows
 | 
			
		||||
	a.sharedWorkspace(&CollaborativeArea{}, id) // create all collaborative areas
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)        // then add on yours
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateOne updates a collaborative area in the database, given its ID and the new data, it automatically share to peers if the workspace is shared
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	res, code, err := utils.GenericUpdateOne(set.(*CollaborativeArea).Trim(), id, a, &CollaborativeArea{})
 | 
			
		||||
	// a.deleteToPeer(res.(*CollaborativeArea))        // delete the collaborative area on the peer
 | 
			
		||||
	a.sharedWorkflow(res.(*CollaborativeArea), id)  // replace all shared workflows
 | 
			
		||||
	a.sharedWorkspace(res.(*CollaborativeArea), id) // replace all collaborative areas (not shared worspace obj but workspace one)
 | 
			
		||||
	// a.sendToPeer(res.(*CollaborativeArea))  // send the collaborative area (collaborative area object) to the peers
 | 
			
		||||
	return res, code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreOne stores a collaborative area in the database, it automatically share to peers if the workspace is shared
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	_, id := (&peer.Peer{}).IsMySelf()  // get the local peer
 | 
			
		||||
	data.(*CollaborativeArea).Clear(id) // set the creator
 | 
			
		||||
	// retrieve or proper peer
 | 
			
		||||
	if data.(*CollaborativeArea).CollaborativeAreaRule != nil {
 | 
			
		||||
		data.(*CollaborativeArea).CollaborativeAreaRule = &CollaborativeAreaRule{}
 | 
			
		||||
	}
 | 
			
		||||
	data.(*CollaborativeArea).CollaborativeAreaRule.Creator = id
 | 
			
		||||
	d, code, err := utils.GenericStoreOne(data.(*CollaborativeArea).Trim(), a)
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		a.sharedWorkflow(d.(*CollaborativeArea), d.GetID())  // create all shared workflows
 | 
			
		||||
		a.sharedWorkspace(d.(*CollaborativeArea), d.GetID()) // create all collaborative areas
 | 
			
		||||
		a.sendToPeer(d.(*CollaborativeArea))                 // send the collaborative area (collaborative area object) to the peers
 | 
			
		||||
	}
 | 
			
		||||
	return data, code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CopyOne copies a CollaborativeArea in the database
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return a.StoreOne(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filterEnrich[T utils.ShallowDBObject](arr []string, isDrafted bool, a utils.Accessor) []T {
 | 
			
		||||
	var new []T
 | 
			
		||||
	res, code, _ := a.Search(&dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{
 | 
			
		||||
			"abstractobject.id": {{Operator: dbs.IN.String(), Value: arr}},
 | 
			
		||||
		},
 | 
			
		||||
	}, "", isDrafted)
 | 
			
		||||
	fmt.Println(res, arr, isDrafted, a)
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		for _, r := range res {
 | 
			
		||||
			new = append(new, r.(T))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return new
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// enrich is a function that enriches the CollaborativeArea with the shared objects
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) enrich(sharedWorkspace *CollaborativeArea, isDrafted bool, request *tools.APIRequest) *CollaborativeArea {
 | 
			
		||||
	sharedWorkspace.SharedWorkspaces = filterEnrich[*workspace.Workspace](sharedWorkspace.Workspaces, isDrafted, a.workspaceAccessor)
 | 
			
		||||
	sharedWorkspace.SharedWorkflows = filterEnrich[*workflow.Workflow](sharedWorkspace.Workflows, isDrafted, a.workflowAccessor)
 | 
			
		||||
	peerskey := []string{}
 | 
			
		||||
	fmt.Println("PEERS 1", sharedWorkspace.AllowedPeersGroup)
 | 
			
		||||
	for k, v := range sharedWorkspace.AllowedPeersGroup {
 | 
			
		||||
		canFound := false
 | 
			
		||||
		for _, t := range request.Groups {
 | 
			
		||||
			if slices.Contains(v, t) {
 | 
			
		||||
				canFound = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println("PEERS 2", canFound, v)
 | 
			
		||||
		if slices.Contains(v, "*") || canFound {
 | 
			
		||||
			peerskey = append(peerskey, k)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("PEERS", peerskey)
 | 
			
		||||
	sharedWorkspace.SharedPeers = filterEnrich[*peer.Peer](peerskey, isDrafted, a.peerAccessor)
 | 
			
		||||
	sharedWorkspace.SharedRules = filterEnrich[*rule.Rule](sharedWorkspace.Rules, isDrafted, a.ruleAccessor)
 | 
			
		||||
	return sharedWorkspace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*CollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return a.enrich(d.(*CollaborativeArea), false, a.Request), 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) LoadAll(isDrafted bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*CollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
 | 
			
		||||
	}, isDrafted, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) Search(filters *dbs.Filters, search string, isDrafted bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*CollaborativeArea](filters, search, (&CollaborativeArea{}).GetObjectFilters(search),
 | 
			
		||||
		func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			return a.enrich(d.(*CollaborativeArea), isDrafted, a.Request)
 | 
			
		||||
		}, isDrafted, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
sharedWorkspace is a function that shares the collaborative area to the peers
 | 
			
		||||
*/
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) sharedWorkspace(shared *CollaborativeArea, id string) {
 | 
			
		||||
	eldest, code, _ := a.LoadOne(id) // get the eldest
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		eld := eldest.(*CollaborativeArea)
 | 
			
		||||
		if eld.Workspaces != nil { // update all your workspaces in the eldest by replacing shared ref by an empty string
 | 
			
		||||
			for _, v := range eld.Workspaces {
 | 
			
		||||
				a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: ""}, v)
 | 
			
		||||
				if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				paccess := (&peer.Peer{})                 // send to all peers
 | 
			
		||||
				for k := range shared.AllowedPeersGroup { // delete the collaborative area on the peer
 | 
			
		||||
					b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.DELETE, nil, a.GetCaller())
 | 
			
		||||
					if err != nil && b == nil {
 | 
			
		||||
						a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if shared.Workspaces != nil {
 | 
			
		||||
		for _, v := range shared.Workspaces { // update all the collaborative areas
 | 
			
		||||
			workspace, code, _ := a.workspaceAccessor.UpdateOne(&workspace.Workspace{Shared: shared.UUID}, v) // add the shared ref to workspace
 | 
			
		||||
			if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKSPACE] == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			for k := range shared.AllowedPeersGroup {
 | 
			
		||||
				if code != 200 {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				paccess := (&peer.Peer{}) // send to all peers, add the collaborative area on the peer
 | 
			
		||||
				s := workspace.Serialize(workspace)
 | 
			
		||||
				s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
 | 
			
		||||
				b, err := paccess.LaunchPeerExecution(k, v, tools.WORKSPACE, tools.POST, s, a.GetCaller())
 | 
			
		||||
				if err != nil && b == nil {
 | 
			
		||||
					a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// deleting on peers before adding, to avoid conflicts on peers side
 | 
			
		||||
	// because you have no reference to the remote collaborative area
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sharedWorkflow is a function that shares the shared workflow to the peers
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) sharedWorkflow(shared *CollaborativeArea, id string) {
 | 
			
		||||
	eldest, code, _ := a.LoadOne(id) // get the eldest
 | 
			
		||||
	if code == 200 {
 | 
			
		||||
		eld := eldest.(*CollaborativeArea)
 | 
			
		||||
		if eld.Workflows != nil {
 | 
			
		||||
			for _, v := range eld.Workflows {
 | 
			
		||||
				data, code, _ := a.workflowAccessor.LoadOne(v)
 | 
			
		||||
				if code == 200 {
 | 
			
		||||
					s := data.(*w.Workflow)
 | 
			
		||||
					new := []string{}
 | 
			
		||||
					for _, id2 := range s.Shared {
 | 
			
		||||
						if id2 != id {
 | 
			
		||||
							new = append(new, id2)
 | 
			
		||||
						}
 | 
			
		||||
					} // kick the shared reference in your old shared workflow
 | 
			
		||||
					n := &w.Workflow{}
 | 
			
		||||
					n.Shared = new
 | 
			
		||||
					a.workflowAccessor.UpdateOne(n, v)
 | 
			
		||||
					if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					paccess := (&peer.Peer{})                 // send to all peers
 | 
			
		||||
					for k := range shared.AllowedPeersGroup { // delete the shared workflow on the peer
 | 
			
		||||
						b, err := paccess.LaunchPeerExecution(k, v, tools.WORKFLOW, tools.DELETE, nil, a.GetCaller())
 | 
			
		||||
						if err != nil && b == nil {
 | 
			
		||||
							a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if shared.Workflows != nil { // update all the shared workflows
 | 
			
		||||
		for _, v := range shared.Workflows {
 | 
			
		||||
			data, code, _ := a.workflowAccessor.LoadOne(v)
 | 
			
		||||
			if code == 200 {
 | 
			
		||||
				s := data.(*w.Workflow)
 | 
			
		||||
				if !slices.Contains(s.Shared, id) {
 | 
			
		||||
					s.Shared = append(s.Shared, id)
 | 
			
		||||
					workflow, code, _ := a.workflowAccessor.UpdateOne(s, v)
 | 
			
		||||
					if a.GetCaller() != nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.WORKFLOW] == nil {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					paccess := (&peer.Peer{})
 | 
			
		||||
					for k := range shared.AllowedPeersGroup { // send to all peers
 | 
			
		||||
						if code == 200 {
 | 
			
		||||
							s := workflow.Serialize(workflow) // add the shared workflow on the peer
 | 
			
		||||
							s["name"] = fmt.Sprintf("%v", s["name"]) + "_" + k
 | 
			
		||||
							b, err := paccess.LaunchPeerExecution(k, shared.UUID, tools.WORKFLOW, tools.POST, s, a.GetCaller())
 | 
			
		||||
							if err != nil && b == nil {
 | 
			
		||||
								a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// deleting on peers before adding, to avoid conflicts on peers side
 | 
			
		||||
	// because you have no reference to the remote shared workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sharedWorkspace is a function that shares the collaborative area to the peers
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) deleteToPeer(shared *CollaborativeArea) {
 | 
			
		||||
	a.contactPeer(shared, tools.POST)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sharedWorkspace is a function that shares the collaborative area to the peers
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) sendToPeer(shared *CollaborativeArea) {
 | 
			
		||||
	a.contactPeer(shared, tools.POST)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *collaborativeAreaMongoAccessor) contactPeer(shared *CollaborativeArea, meth tools.METHOD) {
 | 
			
		||||
	if a.GetCaller() == nil || a.GetCaller().URLS == nil || a.GetCaller().URLS[tools.COLLABORATIVE_AREA] == nil || a.GetCaller().Disabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	paccess := (&peer.Peer{})
 | 
			
		||||
	for k := range shared.AllowedPeersGroup {
 | 
			
		||||
		if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: k}}).IsMySelf(); ok || (shared.IsSent && meth == tools.POST) || (!shared.IsSent && meth != tools.POST) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		shared.IsSent = meth == tools.POST
 | 
			
		||||
		b, err := paccess.LaunchPeerExecution(k, k, tools.COLLABORATIVE_AREA, meth, shared.Serialize(shared), a.GetCaller())
 | 
			
		||||
		if err != nil && b == nil {
 | 
			
		||||
			a.Logger.Error().Msg("Could not send to peer " + k + ". Error: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
package rule
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
@@ -18,39 +16,14 @@ type Rule struct {
 | 
			
		||||
	Actions              []string `json:"actions,omitempty" bson:"actions,omitempty"`         // NOT DEFINITIVE TO SPECIFICATION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Rule) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Rule) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Rule) GetName() string {
 | 
			
		||||
	return d.Name
 | 
			
		||||
func (d *Rule) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Rule) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()
 | 
			
		||||
	data.Init(utils.RULE, caller)
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Rule) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Rule) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
func (d *Rule) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								models/collaborative_area/rules/rule/rule_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								models/collaborative_area/rules/rule/rule_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
package rule
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ruleMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the ruleMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *ruleMongoAccessor {
 | 
			
		||||
	return &ruleMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.RULE.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.RULE,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *ruleMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &Rule{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Rule](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Rule](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Rule](filters, search, (&Rule{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ruleMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package shallow_collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ShallowCollaborativeArea struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	IsSent      bool                   `json:"is_sent" bson:"-"`
 | 
			
		||||
	Version     string                 `json:"version,omitempty" bson:"version,omitempty"`
 | 
			
		||||
	Description string                 `json:"description,omitempty" bson:"description,omitempty" validate:"required"`
 | 
			
		||||
	Attributes  map[string]interface{} `json:"attributes,omitempty" bson:"attributes,omitempty"`
 | 
			
		||||
	Workspaces  []string               `json:"workspaces" bson:"workspaces"`
 | 
			
		||||
	Workflows   []string               `json:"workflows" bson:"workflows"`
 | 
			
		||||
	Peers       []string               `json:"peers" bson:"peers"`
 | 
			
		||||
	Rules       []string               `json:"rules,omitempty" bson:"rules,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ShallowCollaborativeArea) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request)
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package shallow_collaborative_area
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type shallowSharedWorkspaceMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *shallowSharedWorkspaceMongoAccessor {
 | 
			
		||||
	return &shallowSharedWorkspaceMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.COLLABORATIVE_AREA.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,                                              // Set the caller
 | 
			
		||||
			Type:    tools.COLLABORATIVE_AREA,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set.(*ShallowCollaborativeArea), id, a, &ShallowCollaborativeArea{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*ShallowCollaborativeArea), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return a.StoreOne(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*ShallowCollaborativeArea](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*ShallowCollaborativeArea](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *shallowSharedWorkspaceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*ShallowCollaborativeArea](filters, search, (&ShallowCollaborativeArea{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								models/common/enum/infrastructure.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/common/enum/infrastructure.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package enum
 | 
			
		||||
 | 
			
		||||
type InfrastructureType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DOCKER InfrastructureType = iota
 | 
			
		||||
	KUBERNETES
 | 
			
		||||
	SLURM
 | 
			
		||||
	HW
 | 
			
		||||
	CONDOR
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t InfrastructureType) String() string {
 | 
			
		||||
	return [...]string{"DOCKER", "KUBERNETES", "SLURM", "HW", "CONDOR"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// get list of all infrastructure types
 | 
			
		||||
func InfrastructureList() []InfrastructureType {
 | 
			
		||||
	return []InfrastructureType{DOCKER, KUBERNETES, SLURM, HW, CONDOR}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								models/common/enum/size.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								models/common/enum/size.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
package enum
 | 
			
		||||
 | 
			
		||||
type StorageSize int
 | 
			
		||||
 | 
			
		||||
// StorageType - Enum that defines the type of storage
 | 
			
		||||
const (
 | 
			
		||||
	GB StorageSize = iota
 | 
			
		||||
	MB
 | 
			
		||||
	KB
 | 
			
		||||
	TB
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var argoType = [...]string{
 | 
			
		||||
	"Gi",
 | 
			
		||||
	"Mi",
 | 
			
		||||
	"Ki",
 | 
			
		||||
	"Ti",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Size to string
 | 
			
		||||
func (t StorageSize) String() string {
 | 
			
		||||
	return [...]string{"GB", "MB", "KB", "TB"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SizeList() []StorageSize {
 | 
			
		||||
	return []StorageSize{GB, MB, KB, TB}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the StorageResource struct
 | 
			
		||||
func (dma StorageSize) ToArgo() string {
 | 
			
		||||
	return argoType[dma]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// enum of a data type
 | 
			
		||||
type StorageType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	FILE = iota
 | 
			
		||||
	STREAM
 | 
			
		||||
	API
 | 
			
		||||
	DATABASE
 | 
			
		||||
	S3
 | 
			
		||||
	MEMORY
 | 
			
		||||
	HARDWARE
 | 
			
		||||
	AZURE
 | 
			
		||||
	GCS
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// String() - Returns the string representation of the storage type
 | 
			
		||||
func (t StorageType) String() string {
 | 
			
		||||
	return [...]string{"FILE", "STREAM", "API", "DATABASE", "S3", "MEMORY", "HARDWARE", "AZURE", "GCS"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TypeList() []StorageType {
 | 
			
		||||
	return []StorageType{FILE, STREAM, API, DATABASE, S3, MEMORY, HARDWARE, AZURE, GCS}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								models/common/enum/status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								models/common/enum/status.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
package enum
 | 
			
		||||
 | 
			
		||||
type CompletionStatus int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DRAFTED CompletionStatus = iota
 | 
			
		||||
	PENDING
 | 
			
		||||
	CANCEL
 | 
			
		||||
	PARTIAL
 | 
			
		||||
	PAID
 | 
			
		||||
	DISPUTED
 | 
			
		||||
	OVERDUE
 | 
			
		||||
	REFUND
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (d CompletionStatus) String() string {
 | 
			
		||||
	return [...]string{"drafted", "pending", "cancel", "partial", "paid", "disputed", "overdue", "refund"}[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CompletionStatusList() []CompletionStatus {
 | 
			
		||||
	return []CompletionStatus{DRAFTED, PENDING, CANCEL, PARTIAL, PAID, DISPUTED, OVERDUE, REFUND}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BookingStatus int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DRAFT BookingStatus = iota
 | 
			
		||||
	SCHEDULED
 | 
			
		||||
	STARTED
 | 
			
		||||
	FAILURE
 | 
			
		||||
	SUCCESS
 | 
			
		||||
	FORGOTTEN
 | 
			
		||||
	DELAYED
 | 
			
		||||
	CANCELLED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var str = [...]string{
 | 
			
		||||
	"draft",
 | 
			
		||||
	"scheduled",
 | 
			
		||||
	"started",
 | 
			
		||||
	"failure",
 | 
			
		||||
	"success",
 | 
			
		||||
	"forgotten",
 | 
			
		||||
	"delayed",
 | 
			
		||||
	"cancelled",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FromInt(i int) string {
 | 
			
		||||
	return str[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d BookingStatus) String() string {
 | 
			
		||||
	return str[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnumIndex - Creating common behavior - give the type a EnumIndex functio
 | 
			
		||||
func (d BookingStatus) EnumIndex() int {
 | 
			
		||||
	return int(d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List
 | 
			
		||||
func StatusList() []BookingStatus {
 | 
			
		||||
	return []BookingStatus{DRAFT, SCHEDULED, STARTED, FAILURE, SUCCESS, FORGOTTEN, DELAYED, CANCELLED}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								models/common/models/access_configuration.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/common/models/access_configuration.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Container struct {
 | 
			
		||||
	Image   string            `json:"image,omitempty" bson:"image,omitempty"`     // Image is the container image TEMPO
 | 
			
		||||
	Command string            `json:"command,omitempty" bson:"command,omitempty"` // Command is the container command
 | 
			
		||||
	Args    string            `json:"args,omitempty" bson:"args,omitempty"`       // Args is the container arguments
 | 
			
		||||
	Env     map[string]string `json:"env,omitempty" bson:"env,omitempty"`         // Env is the container environment variables
 | 
			
		||||
	Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes
 | 
			
		||||
 | 
			
		||||
	Exposes []Expose `bson:"exposes,omitempty" json:"exposes,omitempty"` // Expose is the execution
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Expose struct {
 | 
			
		||||
	Port    int    `json:"port,omitempty" bson:"port,omitempty"`       // Port is the port
 | 
			
		||||
	Reverse string `json:"reverse,omitempty" bson:"reverse,omitempty"` // Reverse is the reverse
 | 
			
		||||
	PAT     int    `json:"pat,omitempty" bson:"pat,omitempty"`         // PAT is the PAT
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								models/common/models/devices.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/common/models/devices.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// CPU is a struct that represents a CPU
 | 
			
		||||
type CPU struct {
 | 
			
		||||
	Model        string  `bson:"model,omitempty" json:"model,omitempty"`
 | 
			
		||||
	FrequencyGhz float64 `bson:"frequency,omitempty" json:"frequency,omitempty"`
 | 
			
		||||
	Cores        int     `bson:"cores,omitempty" json:"cores,omitempty"`
 | 
			
		||||
	Architecture string  `bson:"architecture,omitempty" json:"architecture,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RAM struct {
 | 
			
		||||
	SizeGb float64 `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"`
 | 
			
		||||
	Ecc    bool    `bson:"ecc" json:"ecc" default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GPU struct {
 | 
			
		||||
	Model    string         `bson:"model,omitempty" json:"model,omitempty"`
 | 
			
		||||
	MemoryGb float64        `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"`
 | 
			
		||||
	Cores    map[string]int `bson:"cores,omitempty" json:"cores,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								models/common/models/inoutputs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								models/common/models/inoutputs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Artifact struct {
 | 
			
		||||
	AttrPath string `json:"attr_path,omitempty" bson:"attr_path,omitempty" validate:"required"`
 | 
			
		||||
	AttrFrom string `json:"from_path,omitempty" bson:"from_path,omitempty"`
 | 
			
		||||
	Readonly bool   `json:"readonly" bson:"readonly" default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Param struct {
 | 
			
		||||
	Name      string `json:"name" bson:"name" validate:"required"`
 | 
			
		||||
	Attr      string `json:"attr,omitempty" bson:"attr,omitempty"`
 | 
			
		||||
	Value     string `json:"value,omitempty" bson:"value,omitempty"`
 | 
			
		||||
	Origin    string `json:"origin,omitempty" bson:"origin,omitempty"`
 | 
			
		||||
	Readonly  bool   `json:"readonly" bson:"readonly" default:"true"`
 | 
			
		||||
	Optionnal bool   `json:"optionnal" bson:"optionnal" default:"true"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InOutputs struct {
 | 
			
		||||
	Params    []Param    `json:"parameters" bson:"parameters"`
 | 
			
		||||
	Artifacts []Artifact `json:"artifacts" bson:"artifacts"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								models/common/models/metric.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/common/models/metric.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type MetricsSnapshot struct {
 | 
			
		||||
	From    string   `json:"origin"`
 | 
			
		||||
	Metrics []Metric `json:"metrics"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Metric struct {
 | 
			
		||||
	Name  string  `json:"name"`
 | 
			
		||||
	Value float64 `json:"value"`
 | 
			
		||||
	Error error   `json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetricResume struct {
 | 
			
		||||
	Delta     float64 `json:"delta"`
 | 
			
		||||
	LastValue float64 `json:"last_value"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								models/common/planner.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								models/common/planner.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetPlannerNearestStart(start time.Time, planned map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest) float64 {
 | 
			
		||||
	near := float64(10000000000)    // set a high value
 | 
			
		||||
	for _, items := range planned { // loop through the planned items
 | 
			
		||||
		for _, priced := range items { // loop through the priced items
 | 
			
		||||
			if priced.GetLocationStart() == nil { // if the start is nil,
 | 
			
		||||
				continue // skip the iteration
 | 
			
		||||
			}
 | 
			
		||||
			newS := priced.GetLocationStart()     // get the start
 | 
			
		||||
			if newS.Sub(start).Seconds() < near { // if the difference between the start and the new start is less than the nearest start
 | 
			
		||||
				near = newS.Sub(start).Seconds()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return near
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetPlannerLongestTime(end *time.Time, planned map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest) float64 {
 | 
			
		||||
	if end == nil {
 | 
			
		||||
		return -1
 | 
			
		||||
	}
 | 
			
		||||
	longestTime := float64(0)
 | 
			
		||||
	for _, priced := range planned[tools.PROCESSING_RESOURCE] {
 | 
			
		||||
		if priced.GetLocationEnd() == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		newS := priced.GetLocationEnd()
 | 
			
		||||
		if end == nil && longestTime < newS.Sub(*end).Seconds() {
 | 
			
		||||
			longestTime = newS.Sub(*end).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		// get the nearest start from start var
 | 
			
		||||
	}
 | 
			
		||||
	return longestTime
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								models/common/pricing/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								models/common/pricing/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package pricing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PricedItemITF interface {
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetType() tools.DataType
 | 
			
		||||
	IsPurchasable() bool
 | 
			
		||||
	IsBooked() bool
 | 
			
		||||
	GetCreatorID() string
 | 
			
		||||
	SelectPricing() PricingProfileITF
 | 
			
		||||
	GetLocationStart() *time.Time
 | 
			
		||||
	SetLocationStart(start time.Time)
 | 
			
		||||
	SetLocationEnd(end time.Time)
 | 
			
		||||
	GetLocationEnd() *time.Time
 | 
			
		||||
	GetExplicitDurationInS() float64
 | 
			
		||||
	GetPrice() (float64, error)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								models/common/pricing/pricing_profile.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										66
									
								
								models/common/pricing/pricing_profile.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
package pricing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PricingProfileITF interface {
 | 
			
		||||
	IsBooked() bool
 | 
			
		||||
	IsPurchasable() bool
 | 
			
		||||
	GetPurchase() BuyingStrategy
 | 
			
		||||
	GetOverrideStrategyValue() int
 | 
			
		||||
	GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RefundType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	REFUND_DEAD_END RefundType = iota
 | 
			
		||||
	REFUND_ON_ERROR
 | 
			
		||||
	REFUND_ON_EARLY_END
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t RefundType) String() string {
 | 
			
		||||
	return [...]string{"REFUND ON DEAD END", "REFUND ON ERROR", "REFUND ON EARLY END"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RefundTypeList() []RefundType {
 | 
			
		||||
	return []RefundType{REFUND_DEAD_END, REFUND_ON_ERROR, REFUND_ON_EARLY_END}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessPricingProfile[T Strategy] struct { // only use for acces such as : DATA && PROCESSING
 | 
			
		||||
	Pricing       PricingStrategy[T] `json:"pricing,omitempty" bson:"pricing,omitempty"`   // Price is the price of the resource
 | 
			
		||||
	DefaultRefund RefundType         `json:"default_refund" bson:"default_refund"`         // DefaultRefund is the default refund type of the pricing
 | 
			
		||||
	RefundRatio   int32              `json:"refund_ratio" bson:"refund_ratio" default:"0"` // RefundRatio is the refund ratio if missing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *AccessPricingProfile[T]) GetOverrideStrategyValue() int {
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExploitPrivilegeStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	BASIC ExploitPrivilegeStrategy = iota
 | 
			
		||||
	GARANTED_ON_DELAY
 | 
			
		||||
	GARANTED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ExploitPrivilegeStrategyList() []ExploitPrivilegeStrategy {
 | 
			
		||||
	return []ExploitPrivilegeStrategy{BASIC, GARANTED_ON_DELAY, GARANTED}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t ExploitPrivilegeStrategy) String() string {
 | 
			
		||||
	return [...]string{"NO GARANTY", "GARANTED ON SPECIFIC DELAY", "GARANTED"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ExploitPricingProfile[T Strategy] struct { // only use for exploit such as : STORAGE, COMPUTE, WORKFLOW
 | 
			
		||||
	AccessPricingProfile[T]
 | 
			
		||||
	AdditionnalRefundTypes []RefundType `json:"refund_types" bson:"refund_types"` // RefundTypes is the refund types of the pricing
 | 
			
		||||
 | 
			
		||||
	PrivilegeStrategy   ExploitPrivilegeStrategy `json:"privilege_strategy,omitempty" bson:"privilege_strategy,omitempty"`       // Strategy is the strategy of the pricing
 | 
			
		||||
	GarantedDelaySecond uint                     `json:"garanted_delay_second,omitempty" bson:"garanted_delay_second,omitempty"` // GarantedDelaySecond is the garanted delay of the pricing
 | 
			
		||||
 | 
			
		||||
	Exceeding      bool  `json:"exceeding" bson:"exceeding"`                         // Exceeding is the exceeding of the bill
 | 
			
		||||
	ExceedingRatio int32 `json:"exceeding_ratio" bson:"exceeding_ratio" default:"0"` // ExceedingRatio is the exceeding ratio of the bill
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										185
									
								
								models/common/pricing/pricing_strategy.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										185
									
								
								models/common/pricing/pricing_strategy.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
package pricing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type BillingStrategy int // BAM BAM
 | 
			
		||||
 | 
			
		||||
// should except... on
 | 
			
		||||
const (
 | 
			
		||||
	BILL_ONCE BillingStrategy = iota // is a permanent buying ( predictible )
 | 
			
		||||
	BILL_PER_WEEK
 | 
			
		||||
	BILL_PER_MONTH
 | 
			
		||||
	BILL_PER_YEAR
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t BillingStrategy) IsBillingStrategyAllowed(bs int) (BillingStrategy, bool) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case BILL_ONCE:
 | 
			
		||||
		return BILL_ONCE, bs == 0
 | 
			
		||||
	case BILL_PER_WEEK:
 | 
			
		||||
	case BILL_PER_MONTH:
 | 
			
		||||
	case BILL_PER_YEAR:
 | 
			
		||||
		return t, bs != 0
 | 
			
		||||
	}
 | 
			
		||||
	return t, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t BillingStrategy) String() string {
 | 
			
		||||
	return [...]string{"BILL_ONCE", "BILL_PER_WEEK", "BILL_PER_MONTH", "BILL_PER_YEAR"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BillingStrategyList() []BillingStrategy {
 | 
			
		||||
	return []BillingStrategy{BILL_ONCE, BILL_PER_WEEK, BILL_PER_MONTH, BILL_PER_YEAR}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BuyingStrategy int
 | 
			
		||||
 | 
			
		||||
// should except... on
 | 
			
		||||
const (
 | 
			
		||||
	PERMANENT              BuyingStrategy = iota // is a permanent buying ( predictible )
 | 
			
		||||
	UNDEFINED_SUBSCRIPTION                       // a endless subscription ( unpredictible )
 | 
			
		||||
	SUBSCRIPTION                                 // a defined subscription ( predictible )
 | 
			
		||||
	// PAY_PER_USE                                  // per request. ( unpredictible )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t BuyingStrategy) String() string {
 | 
			
		||||
	return [...]string{"PERMANENT", "UNDEFINED_SUBSCRIPTION", "SUBSCRIPTION"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t BuyingStrategy) IsBillingStrategyAllowed(bs BillingStrategy) (BillingStrategy, bool) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case PERMANENT:
 | 
			
		||||
		return BILL_ONCE, bs == BILL_ONCE
 | 
			
		||||
	case UNDEFINED_SUBSCRIPTION:
 | 
			
		||||
		return BILL_PER_MONTH, bs != BILL_ONCE
 | 
			
		||||
	case SUBSCRIPTION:
 | 
			
		||||
		/*case PAY_PER_USE:
 | 
			
		||||
		return bs, true*/
 | 
			
		||||
	}
 | 
			
		||||
	return bs, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BuyingStrategyList() []BuyingStrategy {
 | 
			
		||||
	return []BuyingStrategy{PERMANENT, UNDEFINED_SUBSCRIPTION, SUBSCRIPTION}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Strategy interface {
 | 
			
		||||
	GetStrategy() string
 | 
			
		||||
	GetStrategyValue() int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TimePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ONCE TimePricingStrategy = iota
 | 
			
		||||
	PER_SECOND
 | 
			
		||||
	PER_MINUTE
 | 
			
		||||
	PER_HOUR
 | 
			
		||||
	PER_DAY
 | 
			
		||||
	PER_WEEK
 | 
			
		||||
	PER_MONTH
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func IsTimeStrategy(i int) bool {
 | 
			
		||||
	return len(TimePricingStrategyList()) < i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TimePricingStrategy) String() string {
 | 
			
		||||
	return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TimePricingStrategyListStr() []string {
 | 
			
		||||
	return []string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TimePricingStrategyList() []TimePricingStrategy {
 | 
			
		||||
	return []TimePricingStrategy{ONCE, PER_SECOND, PER_MINUTE, PER_HOUR, PER_DAY, PER_WEEK, PER_MONTH}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TimePricingStrategy) GetStrategy() string {
 | 
			
		||||
	return [...]string{"ONCE", "PER_SECOND", "PER_MINUTE", "PER_HOUR", "PER_DAY", "PER_WEEK", "PER_MONTH"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t TimePricingStrategy) GetStrategyValue() int {
 | 
			
		||||
	return int(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAverageTimeInSecond(averageTimeInSecond float64, start time.Time, end *time.Time) float64 {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	after := now.Add(time.Duration(averageTimeInSecond) * time.Second)
 | 
			
		||||
 | 
			
		||||
	fromAverageDuration := after.Sub(now).Seconds()
 | 
			
		||||
	var tEnd time.Time
 | 
			
		||||
	if end == nil {
 | 
			
		||||
		tEnd = start.Add(1 * time.Hour)
 | 
			
		||||
	} else {
 | 
			
		||||
		tEnd = *end
 | 
			
		||||
	}
 | 
			
		||||
	fromDateDuration := tEnd.Sub(start).Seconds()
 | 
			
		||||
 | 
			
		||||
	if fromAverageDuration > fromDateDuration {
 | 
			
		||||
		return fromAverageDuration
 | 
			
		||||
	}
 | 
			
		||||
	return fromDateDuration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BookingEstimation(t TimePricingStrategy, price float64, locationDurationInSecond float64, start time.Time, end *time.Time) (float64, error) {
 | 
			
		||||
	locationDurationInSecond = getAverageTimeInSecond(locationDurationInSecond, start, end)
 | 
			
		||||
	priceStr := fmt.Sprintf("%v", price)
 | 
			
		||||
	p, err := strconv.ParseFloat(priceStr, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	switch t {
 | 
			
		||||
	case ONCE:
 | 
			
		||||
		return p, nil
 | 
			
		||||
	case PER_HOUR:
 | 
			
		||||
		return p * float64(locationDurationInSecond/3600), nil
 | 
			
		||||
	case PER_MINUTE:
 | 
			
		||||
		return p * float64(locationDurationInSecond/60), nil
 | 
			
		||||
	case PER_SECOND:
 | 
			
		||||
		return p * locationDurationInSecond, nil
 | 
			
		||||
	case PER_DAY:
 | 
			
		||||
		return p * float64(locationDurationInSecond/86400), nil
 | 
			
		||||
	case PER_WEEK:
 | 
			
		||||
		return p * float64(locationDurationInSecond/604800), nil
 | 
			
		||||
	case PER_MONTH:
 | 
			
		||||
		return p * float64(locationDurationInSecond/2592000), nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("pricing strategy not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// may suppress in pricing strategy -> to set in map
 | 
			
		||||
type PricingStrategy[T Strategy] struct {
 | 
			
		||||
	Price               float64             `json:"price" bson:"price" default:"0"`                                 // Price is the Price of the pricing
 | 
			
		||||
	Currency            string              `json:"currency" bson:"currency" default:"USD"`                         // Currency is the currency of the pricing
 | 
			
		||||
	BuyingStrategy      BuyingStrategy      `json:"buying_strategy" bson:"buying_strategy" default:"0"`             // BuyingStrategy is the buying strategy of the pricing
 | 
			
		||||
	TimePricingStrategy TimePricingStrategy `json:"time_pricing_strategy" bson:"time_pricing_strategy" default:"0"` // TimePricingStrategy is the time pricing strategy of the pricing
 | 
			
		||||
	OverrideStrategy    T                   `json:"override_strategy" bson:"override_strategy" default:"-1"`        // Modulation is the modulation of the pricing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) {
 | 
			
		||||
	if p.BuyingStrategy == SUBSCRIPTION {
 | 
			
		||||
		return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
 | 
			
		||||
	} else if p.BuyingStrategy == PERMANENT {
 | 
			
		||||
		return p.Price, nil
 | 
			
		||||
	}
 | 
			
		||||
	return p.Price * float64(amountOfData), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetBuyingStrategy() BuyingStrategy {
 | 
			
		||||
	return p.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy {
 | 
			
		||||
	return p.TimePricingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PricingStrategy[T]) GetOverrideStrategy() T {
 | 
			
		||||
	return p.OverrideStrategy
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								models/common/pricing/tests/pricing_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								models/common/pricing/tests/pricing_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
package pricing_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DummyStrategy int
 | 
			
		||||
 | 
			
		||||
func (d DummyStrategy) GetStrategy() string   { return "DUMMY" }
 | 
			
		||||
func (d DummyStrategy) GetStrategyValue() int { return int(d) }
 | 
			
		||||
 | 
			
		||||
func TestBuyingStrategy_String(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "UNLIMITED", pricing.PERMANENT.String())
 | 
			
		||||
	assert.Equal(t, "SUBSCRIPTION", pricing.SUBSCRIPTION.String())
 | 
			
		||||
	//assert.Equal(t, "PAY PER USE", pricing.PAY_PER_USE.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuyingStrategyList(t *testing.T) {
 | 
			
		||||
	list := pricing.BuyingStrategyList()
 | 
			
		||||
	assert.Equal(t, 3, len(list))
 | 
			
		||||
	assert.Contains(t, list, pricing.SUBSCRIPTION)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTimePricingStrategy_String(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "ONCE", pricing.ONCE.String())
 | 
			
		||||
	assert.Equal(t, "PER SECOND", pricing.PER_SECOND.String())
 | 
			
		||||
	assert.Equal(t, "PER MONTH", pricing.PER_MONTH.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTimePricingStrategyList(t *testing.T) {
 | 
			
		||||
	list := pricing.TimePricingStrategyList()
 | 
			
		||||
	assert.Equal(t, 7, len(list))
 | 
			
		||||
	assert.Contains(t, list, pricing.PER_DAY)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTimePricingStrategy_Methods(t *testing.T) {
 | 
			
		||||
	ts := pricing.PER_MINUTE
 | 
			
		||||
	assert.Equal(t, "PER_MINUTE", ts.GetStrategy())
 | 
			
		||||
	assert.Equal(t, 2, ts.GetStrategyValue())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_getAverageTimeInSecond_WithEnd(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(30 * time.Minute)
 | 
			
		||||
 | 
			
		||||
	_, err := pricing.BookingEstimation(pricing.PER_MINUTE, 2.0, 1200, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_getAverageTimeInSecond_WithoutEnd(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// getAverageTimeInSecond is tested via BookingEstimation
 | 
			
		||||
	price, err := pricing.BookingEstimation(pricing.PER_HOUR, 10.0, 100, start, nil)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, price > 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBookingEstimation(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(2 * time.Hour)
 | 
			
		||||
	strategies := map[pricing.TimePricingStrategy]float64{
 | 
			
		||||
		pricing.ONCE:       50,
 | 
			
		||||
		pricing.PER_HOUR:   10,
 | 
			
		||||
		pricing.PER_MINUTE: 1,
 | 
			
		||||
		pricing.PER_SECOND: 0.1,
 | 
			
		||||
		pricing.PER_DAY:    100,
 | 
			
		||||
		pricing.PER_WEEK:   500,
 | 
			
		||||
		pricing.PER_MONTH:  2000,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for strategy, price := range strategies {
 | 
			
		||||
		t.Run(strategy.String(), func(t *testing.T) {
 | 
			
		||||
			cost, err := pricing.BookingEstimation(strategy, price, 3600, start, &end)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.True(t, cost >= 0)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := pricing.BookingEstimation(999, 10, 3600, start, &end)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricingStrategy_Getters(t *testing.T) {
 | 
			
		||||
	ps := pricing.PricingStrategy[DummyStrategy]{
 | 
			
		||||
		Price:               20,
 | 
			
		||||
		Currency:            "USD",
 | 
			
		||||
		BuyingStrategy:      pricing.SUBSCRIPTION,
 | 
			
		||||
		TimePricingStrategy: pricing.PER_MINUTE,
 | 
			
		||||
		OverrideStrategy:    DummyStrategy(1),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, pricing.SUBSCRIPTION, ps.GetBuyingStrategy())
 | 
			
		||||
	assert.Equal(t, pricing.PER_MINUTE, ps.GetTimePricingStrategy())
 | 
			
		||||
	assert.Equal(t, DummyStrategy(1), ps.GetOverrideStrategy())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricingStrategy_GetPrice(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	// SUBSCRIPTION case
 | 
			
		||||
	ps := pricing.PricingStrategy[DummyStrategy]{
 | 
			
		||||
		Price:               5,
 | 
			
		||||
		BuyingStrategy:      pricing.SUBSCRIPTION,
 | 
			
		||||
		TimePricingStrategy: pricing.PER_HOUR,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p, err := ps.GetPrice(2, 3600, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, p > 0)
 | 
			
		||||
 | 
			
		||||
	// UNLIMITED case
 | 
			
		||||
	ps.BuyingStrategy = pricing.PERMANENT
 | 
			
		||||
	p, err = ps.GetPrice(10, 0, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 5.0, p)
 | 
			
		||||
 | 
			
		||||
	// PAY_PER_USE case
 | 
			
		||||
	//ps.BuyingStrategy = pricing.PAY_PER_USE
 | 
			
		||||
	p, err = ps.GetPrice(3, 0, start, &end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 15.0, p)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								models/live/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								models/live/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LiveInterface interface {
 | 
			
		||||
	utils.DBObject
 | 
			
		||||
	GetMonitorPath() string
 | 
			
		||||
	GetResourcesID() []string
 | 
			
		||||
	SetResourcesID(string)
 | 
			
		||||
	GetResourceAccessor(request *tools.APIRequest) utils.Accessor
 | 
			
		||||
	GetResource() resources.ResourceInterface
 | 
			
		||||
	GetResourceInstance() resources.ResourceInstanceITF
 | 
			
		||||
	SetResourceInstance(res resources.ResourceInterface, i resources.ResourceInstanceITF) resources.ResourceInterface
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								models/live/live.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								models/live/live.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/biter777/countries"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LiveDatacenter is a struct that represents a compute units in your datacenters
 | 
			
		||||
 */
 | 
			
		||||
type Credentials struct {
 | 
			
		||||
	Login string `json:"login,omitempty" bson:"login,omitempty"`
 | 
			
		||||
	Pass  string `json:"password,omitempty" bson:"password,omitempty"`
 | 
			
		||||
	Token string `json:"token,omitempty" bson:"token,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Certs struct {
 | 
			
		||||
	AuthorityCertificate string `json:"authority_certificate,omitempty" bson:"authority_certificate,omitempty"`
 | 
			
		||||
	ClientCertificate    string `json:"client_certificate,omitempty" bson:"client_certificate,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LiveCerts struct {
 | 
			
		||||
	Host string `json:"host,omitempty" bson:"host,omitempty"`
 | 
			
		||||
	Port string `json:"port,omitempty" bson:"port,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Certificates *Certs       `json:"certs,omitempty" bson:"certs,omitempty"`
 | 
			
		||||
	Credentials  *Credentials `json:"creds,omitempty" bson:"creds,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO in the future multiple type of certs depending of infra type
 | 
			
		||||
 | 
			
		||||
type AbstractLive struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	Certs LiveCerts `json:"certs,omitempty" bson:"certs,omitempty"`
 | 
			
		||||
 | 
			
		||||
	MonitorPath    string                `json:"monitor_path,omitempty" bson:"monitor_path,omitempty"`
 | 
			
		||||
	Location       resources.GeoPoint    `json:"location,omitempty" bson:"location,omitempty"`
 | 
			
		||||
	Country        countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
 | 
			
		||||
	AccessProtocol string                `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
 | 
			
		||||
	ResourcesID    []string              `json:"resources_id" bson:"resources_id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractLive) GetMonitorPath() string {
 | 
			
		||||
	return d.MonitorPath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractLive) GetResourcesID() []string {
 | 
			
		||||
	return d.ResourcesID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractLive) SetResourcesID(id string) {
 | 
			
		||||
	if !slices.Contains(d.ResourcesID, id) {
 | 
			
		||||
		d.ResourcesID = append(d.ResourcesID, id)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractLive) GetResourceType() tools.DataType {
 | 
			
		||||
	return tools.INVALID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractLive) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractLive) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft ComputeUnits can be deleted
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								models/live/live_datacenter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								models/live/live_datacenter.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LiveDatacenter is a struct that represents a compute units in your datacenters
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type LiveDatacenter struct {
 | 
			
		||||
	AbstractLive
 | 
			
		||||
 | 
			
		||||
	StorageType enum.StorageType `bson:"storage_type" json:"storage_type" default:"-1"` // Type is the type of the storage
 | 
			
		||||
	Acronym     string           `bson:"acronym,omitempty" json:"acronym,omitempty"`    // Acronym is the acronym of the storage
 | 
			
		||||
 | 
			
		||||
	Architecture       string                   `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture
 | 
			
		||||
	Infrastructure     enum.InfrastructureType  `json:"infrastructure" bson:"infrastructure" default:"-1"`    // Infrastructure is the infrastructure
 | 
			
		||||
	Source             string                   `json:"source,omitempty" bson:"source,omitempty"`             // Source is the source of the resource
 | 
			
		||||
	SecurityLevel      string                   `json:"security_level,omitempty" bson:"security_level,omitempty"`
 | 
			
		||||
	PowerSources       []string                 `json:"power_sources,omitempty" bson:"power_sources,omitempty"`
 | 
			
		||||
	AnnualCO2Emissions float64                  `json:"annual_co2_emissions,omitempty" bson:"co2_emissions,omitempty"`
 | 
			
		||||
	CPUs               map[string]*models.CPU   `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs               map[string]*models.GPU   `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
	Nodes              []*resources.ComputeNode `json:"nodes,omitempty" bson:"nodes,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveDatacenter) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*LiveDatacenter](tools.LIVE_DATACENTER, request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveDatacenter) GetResourceAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return resources.NewAccessor[*resources.ComputeResource](tools.COMPUTE_RESOURCE, request, func() utils.DBObject { return &resources.ComputeResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveDatacenter) GetResource() resources.ResourceInterface {
 | 
			
		||||
	return &resources.ComputeResource{}
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveDatacenter) GetResourceInstance() resources.ResourceInstanceITF {
 | 
			
		||||
	return &resources.ComputeResourceInstance{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveDatacenter) SetResourceInstance(res resources.ResourceInterface, i resources.ResourceInstanceITF) resources.ResourceInterface {
 | 
			
		||||
	r := res.(*resources.ComputeResource)
 | 
			
		||||
	r.Instances = append(r.Instances, i.(*resources.ComputeResourceInstance))
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								models/live/live_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								models/live/live_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type computeUnitsMongoAccessor[T LiveInterface] struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the computeUnitsMongoAccessor
 | 
			
		||||
func NewAccessor[T LiveInterface](t tools.DataType, request *tools.APIRequest) *computeUnitsMongoAccessor[T] {
 | 
			
		||||
	return &computeUnitsMongoAccessor[T]{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    t,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	// should verify if a source is existing...
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &LiveDatacenter{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data.(*LiveDatacenter), a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	// is a publisher... that become a resources.
 | 
			
		||||
	if data.IsDrafted() {
 | 
			
		||||
		return nil, 422, errors.New("can't publish a drafted compute units")
 | 
			
		||||
	}
 | 
			
		||||
	live := data.(T)
 | 
			
		||||
	if live.GetMonitorPath() == "" || live.GetID() != "" {
 | 
			
		||||
		return nil, 422, errors.New("publishing is only allowed is it can be monitored and be accessible")
 | 
			
		||||
	}
 | 
			
		||||
	if res, code, err := a.LoadOne(live.GetID()); err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	} else {
 | 
			
		||||
		live = res.(T)
 | 
			
		||||
	}
 | 
			
		||||
	resAccess := live.GetResourceAccessor(a.Request)
 | 
			
		||||
	instance := live.GetResourceInstance()
 | 
			
		||||
	b, _ := json.Marshal(live)
 | 
			
		||||
	json.Unmarshal(b, instance)
 | 
			
		||||
 | 
			
		||||
	if len(live.GetResourcesID()) > 0 {
 | 
			
		||||
		for _, r := range live.GetResourcesID() {
 | 
			
		||||
			// TODO dependent of a existing resource
 | 
			
		||||
			res, code, err := resAccess.LoadOne(r)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				return nil, code, err
 | 
			
		||||
			}
 | 
			
		||||
			existingResource := live.GetResource()
 | 
			
		||||
			b, _ := json.Marshal(res)
 | 
			
		||||
			json.Unmarshal(b, existingResource)
 | 
			
		||||
			live.SetResourceInstance(existingResource, instance)
 | 
			
		||||
			resAccess.UpdateOne(existingResource, existingResource.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		if live.GetID() != "" {
 | 
			
		||||
			return a.LoadOne(live.GetID())
 | 
			
		||||
		} else {
 | 
			
		||||
			return a.StoreOne(live)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		r := live.GetResource()
 | 
			
		||||
		b, _ := json.Marshal(live)
 | 
			
		||||
		json.Unmarshal(b, &r)
 | 
			
		||||
		live.SetResourceInstance(r, instance)
 | 
			
		||||
		res, code, err := resAccess.StoreOne(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, code, err
 | 
			
		||||
		}
 | 
			
		||||
		live.SetResourcesID(res.GetID())
 | 
			
		||||
		if live.GetID() != "" {
 | 
			
		||||
			return a.UpdateOne(live, live.GetID())
 | 
			
		||||
		} else {
 | 
			
		||||
			return a.StoreOne(live)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[T](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*LiveDatacenter](filters, search, (&LiveDatacenter{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *computeUnitsMongoAccessor[T]) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								models/live/live_storage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								models/live/live_storage.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
package live
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* LiveStorage is a struct that represents a compute units in your datacenters
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type LiveStorage struct {
 | 
			
		||||
	AbstractLive
 | 
			
		||||
 | 
			
		||||
	Source        string           `bson:"source,omitempty" json:"source,omitempty"` // Source is the source of the storage
 | 
			
		||||
	Path          string           `bson:"path,omitempty" json:"path,omitempty"`     // Path is the store folders in the source
 | 
			
		||||
	Local         bool             `bson:"local" json:"local"`
 | 
			
		||||
	SecurityLevel string           `bson:"security_level,omitempty" json:"security_level,omitempty"`
 | 
			
		||||
	SizeType      enum.StorageSize `bson:"size_type" json:"size_type" default:"0"`           // SizeType is the type of the storage size
 | 
			
		||||
	SizeGB        int64            `bson:"size,omitempty" json:"size,omitempty"`             // Size is the size of the storage
 | 
			
		||||
	Encryption    bool             `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
 | 
			
		||||
	Redundancy    string           `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
 | 
			
		||||
	Throughput    string           `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveStorage) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*LiveStorage](tools.LIVE_STORAGE, request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveStorage) GetResourceAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return resources.NewAccessor[*resources.ComputeResource](tools.STORAGE_RESOURCE, request, func() utils.DBObject { return &resources.StorageResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveStorage) GetResource() resources.ResourceInterface {
 | 
			
		||||
	return &resources.StorageResource{}
 | 
			
		||||
}
 | 
			
		||||
func (d *LiveStorage) GetResourceInstance() resources.ResourceInstanceITF {
 | 
			
		||||
	return &resources.StorageResourceInstance{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *LiveStorage) SetResourceInstance(res resources.ResourceInterface, i resources.ResourceInstanceITF) resources.ResourceInterface {
 | 
			
		||||
	r := res.(*resources.StorageResource)
 | 
			
		||||
	r.Instances = append(r.Instances, i.(*resources.StorageResourceInstance))
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
@@ -2,57 +2,63 @@ package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/bill"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/live"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/order"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/rules/rule"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	d "cloud.o-forge.io/core/oc-lib/models/resources/data"
 | 
			
		||||
	dc "cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
 | 
			
		||||
	p "cloud.o-forge.io/core/oc-lib/models/resources/processing"
 | 
			
		||||
	s "cloud.o-forge.io/core/oc-lib/models/resources/storage"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/resources/workflow"
 | 
			
		||||
	resource "cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	w2 "cloud.o-forge.io/core/oc-lib/models/workflow"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	w3 "cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	shared_workspace "cloud.o-forge.io/core/oc-lib/models/workspace/shared"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace/shared/rules/rule"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
This package contains the models used in the application
 | 
			
		||||
It's used to create the models dynamically
 | 
			
		||||
*/
 | 
			
		||||
var models = map[string]func() utils.DBObject{
 | 
			
		||||
	utils.WORKFLOW_RESOURCE.String():   func() utils.DBObject { return &w.WorkflowResource{} },
 | 
			
		||||
	utils.DATA_RESOURCE.String():       func() utils.DBObject { return &d.DataResource{} },
 | 
			
		||||
	utils.DATACENTER_RESOURCE.String(): func() utils.DBObject { return &dc.DatacenterResource{} },
 | 
			
		||||
	utils.STORAGE_RESOURCE.String():    func() utils.DBObject { return &s.StorageResource{} },
 | 
			
		||||
	utils.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &p.ProcessingResource{} },
 | 
			
		||||
	utils.WORKFLOW.String():            func() utils.DBObject { return &w2.Workflow{} },
 | 
			
		||||
	utils.WORKFLOW_EXECUTION.String():  func() utils.DBObject { return &workflow_execution.WorkflowExecution{} },
 | 
			
		||||
	utils.WORKSPACE.String():           func() utils.DBObject { return &w3.Workspace{} },
 | 
			
		||||
	utils.RESOURCE_MODEL.String():      func() utils.DBObject { return &resource_model.ResourceModel{} },
 | 
			
		||||
	utils.PEER.String():                func() utils.DBObject { return &peer.Peer{} },
 | 
			
		||||
	utils.SHARED_WORKSPACE.String():    func() utils.DBObject { return &shared_workspace.SharedWorkspace{} },
 | 
			
		||||
	utils.RULE.String():                func() utils.DBObject { return &rule.Rule{} },
 | 
			
		||||
	utils.BOOKING.String():             func() utils.DBObject { return &booking.Booking{} },
 | 
			
		||||
var ModelsCatalog = map[string]func() utils.DBObject{
 | 
			
		||||
	tools.WORKFLOW_RESOURCE.String():   func() utils.DBObject { return &resource.WorkflowResource{} },
 | 
			
		||||
	tools.DATA_RESOURCE.String():       func() utils.DBObject { return &resource.DataResource{} },
 | 
			
		||||
	tools.COMPUTE_RESOURCE.String():    func() utils.DBObject { return &resource.ComputeResource{} },
 | 
			
		||||
	tools.STORAGE_RESOURCE.String():    func() utils.DBObject { return &resource.StorageResource{} },
 | 
			
		||||
	tools.PROCESSING_RESOURCE.String(): func() utils.DBObject { return &resource.ProcessingResource{} },
 | 
			
		||||
	tools.WORKFLOW.String():            func() utils.DBObject { return &w2.Workflow{} },
 | 
			
		||||
	tools.WORKFLOW_EXECUTION.String():  func() utils.DBObject { return &workflow_execution.WorkflowExecution{} },
 | 
			
		||||
	tools.WORKSPACE.String():           func() utils.DBObject { return &w3.Workspace{} },
 | 
			
		||||
	tools.PEER.String():                func() utils.DBObject { return &peer.Peer{} },
 | 
			
		||||
	tools.COLLABORATIVE_AREA.String():  func() utils.DBObject { return &collaborative_area.CollaborativeArea{} },
 | 
			
		||||
	tools.RULE.String():                func() utils.DBObject { return &rule.Rule{} },
 | 
			
		||||
	tools.BOOKING.String():             func() utils.DBObject { return &booking.Booking{} },
 | 
			
		||||
	tools.WORKFLOW_HISTORY.String():    func() utils.DBObject { return &w2.WorkflowHistory{} },
 | 
			
		||||
	tools.WORKSPACE_HISTORY.String():   func() utils.DBObject { return &w3.WorkspaceHistory{} },
 | 
			
		||||
	tools.ORDER.String():               func() utils.DBObject { return &order.Order{} },
 | 
			
		||||
	tools.PURCHASE_RESOURCE.String():   func() utils.DBObject { return &purchase_resource.PurchaseResource{} },
 | 
			
		||||
	tools.LIVE_DATACENTER.String():     func() utils.DBObject { return &live.LiveDatacenter{} },
 | 
			
		||||
	tools.LIVE_STORAGE.String():        func() utils.DBObject { return &live.LiveStorage{} },
 | 
			
		||||
	tools.BILL.String():                func() utils.DBObject { return &bill.Bill{} },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Model returns the model object based on the model type
 | 
			
		||||
func Model(model int) utils.DBObject {
 | 
			
		||||
	log := logs.GetLogger()
 | 
			
		||||
	if _, ok := models[utils.FromInt(model)]; ok {
 | 
			
		||||
		return models[utils.FromInt(model)]()
 | 
			
		||||
	if _, ok := ModelsCatalog[tools.FromInt(model)]; ok {
 | 
			
		||||
		return ModelsCatalog[tools.FromInt(model)]()
 | 
			
		||||
	}
 | 
			
		||||
	log.Error().Msg("Can't find model " + utils.FromInt(model) + ".")
 | 
			
		||||
	log.Error().Msg("Can't find model " + tools.FromInt(model) + ".")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetModelsNames returns the names of the models
 | 
			
		||||
func GetModelsNames() []string {
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for name := range models {
 | 
			
		||||
	for name := range ModelsCatalog {
 | 
			
		||||
		names = append(names, name)
 | 
			
		||||
	}
 | 
			
		||||
	return names
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								models/order/order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								models/order/order.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
package order
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/booking"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Booking is a struct that represents a booking
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
type Order struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	ExecutionsID string                                `json:"executions_id" bson:"executions_id" validate:"required"`
 | 
			
		||||
	Status       enum.CompletionStatus                 `json:"status" bson:"status" default:"0"`
 | 
			
		||||
	Purchases    []*purchase_resource.PurchaseResource `json:"purchases" bson:"purchases"`
 | 
			
		||||
	Bookings     []*booking.Booking                    `json:"bookings" bson:"bookings"`
 | 
			
		||||
 | 
			
		||||
	Billing map[pricing.BillingStrategy][]*booking.Booking `json:"billing" bson:"billing"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Order) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Order) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if !r.IsDraft && r.Status != set.(*Order).Status {
 | 
			
		||||
		return true, &Order{Status: set.(*Order).Status} // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return r.IsDraft, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Order) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft order can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Order) Quantity() int {
 | 
			
		||||
	return len(o.Purchases) + len(o.Purchases)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Order) SetName() {
 | 
			
		||||
	d.Name = d.UUID + "_order_" + "_" + time.Now().UTC().Format("2006-01-02T15:04:05")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Order) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								models/order/order_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								models/order/order_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
package order
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type orderMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the orderMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *orderMongoAccessor {
 | 
			
		||||
	return &orderMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.ORDER.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.ORDER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *orderMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &Order{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data,a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return nil, 404, errors.New("Not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Order](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Order](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Order](filters, search, (&Order{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *orderMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								models/order/tests/order_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								models/order/tests/order_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package tests
 | 
			
		||||
@@ -1,24 +1,46 @@
 | 
			
		||||
package peer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/static"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// now write a go enum for the state partner with self, blacklist, partner
 | 
			
		||||
 | 
			
		||||
type PeerState int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	NONE PeerState = iota
 | 
			
		||||
	SELF
 | 
			
		||||
	PARTNER
 | 
			
		||||
	BLACKLIST
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (m PeerState) String() string {
 | 
			
		||||
	return [...]string{"NONE", "SELF", "PARTNER", "BLACKLIST"}[m]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m PeerState) EnumIndex() int {
 | 
			
		||||
	return int(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Peer is a struct that represents a peer
 | 
			
		||||
type Peer struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	Url             string          `json:"url,omitempty" bson:"url,omitempty" validate:"required,base64url"` // Url is the URL of the peer (base64url)
 | 
			
		||||
	PublicKey       string          `json:"public_key,omitempty" bson:"public_key,omitempty"`                 // PublicKey is the public key of the peer
 | 
			
		||||
	Services        map[string]int  `json:"services,omitempty" bson:"services,omitempty"`                     // Services is the services of the peer
 | 
			
		||||
	Url             string          `json:"url" bson:"url" validate:"required"`                       // Url is the URL of the peer (base64url)
 | 
			
		||||
	WalletAddress   string          `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer
 | 
			
		||||
	PublicKey       string          `json:"public_key" bson:"public_key" validate:"required"`         // PublicKey is the public key of the peer
 | 
			
		||||
	State           PeerState       `json:"state" bson:"state" default:"0"`
 | 
			
		||||
	ServicesState   map[string]int  `json:"services_state,omitempty" bson:"services_state,omitempty"`
 | 
			
		||||
	FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Peer) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddExecution adds an execution to the list of failed executions
 | 
			
		||||
func (ao *Peer) AddExecution(exec PeerExecution) {
 | 
			
		||||
	found := false
 | 
			
		||||
@@ -45,50 +67,25 @@ func (ao *Peer) RemoveExecution(exec PeerExecution) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsMySelf checks if the peer is the local peer
 | 
			
		||||
func (ao *Peer) IsMySelf() bool {
 | 
			
		||||
	id, _ := static.GetMyLocalJsonPeer()
 | 
			
		||||
	return ao.UUID == id
 | 
			
		||||
func (p *Peer) IsMySelf() (bool, string) {
 | 
			
		||||
	d, code, err := NewAccessor(nil).Search(nil, SELF.String(), p.IsDraft)
 | 
			
		||||
	if code != 200 || err != nil || len(d) == 0 {
 | 
			
		||||
		return false, ""
 | 
			
		||||
	}
 | 
			
		||||
	id := d[0].GetID()
 | 
			
		||||
	return p.UUID == id, id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LaunchPeerExecution launches an execution on a peer
 | 
			
		||||
func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt utils.DataType, method tools.METHOD, body map[string]interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) {
 | 
			
		||||
func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (map[string]interface{}, error) {
 | 
			
		||||
	p.UUID = peerID
 | 
			
		||||
	return cache.LaunchPeerExecution(peerID, dataID, dt, method, body, caller) // Launch the execution on the peer through the cache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Peer) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Peer) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Peer) GetName() string {
 | 
			
		||||
	return d.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Peer) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                 // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.PEER, caller) // Initialize the accessor with the PEER model type
 | 
			
		||||
func (d *Peer) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	data := NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Peer) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Peer) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
func (r *Peer) CanDelete() bool {
 | 
			
		||||
	return false // only draft order can be deleted
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,8 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -18,7 +16,7 @@ import (
 | 
			
		||||
type PeerExecution struct {
 | 
			
		||||
	Method   string      `json:"method" bson:"method"`
 | 
			
		||||
	Url      string      `json:"url" bson:"url"`
 | 
			
		||||
	Body     map[string]interface{} `json:"body" bson:"body"`
 | 
			
		||||
	Body     interface{} `json:"body" bson:"body"`
 | 
			
		||||
	DataType int         `json:"data_type" bson:"data_type"`
 | 
			
		||||
	DataID   string      `json:"data_id" bson:"data_id"`
 | 
			
		||||
}
 | 
			
		||||
@@ -30,87 +28,69 @@ type PeerCache struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// urlFormat formats the URL of the peer with the data type API function
 | 
			
		||||
func (p *PeerCache) urlFormat(url string, dt utils.DataType) string {
 | 
			
		||||
	// localhost is replaced by the local peer URL
 | 
			
		||||
	// because localhost must collide on a web request security protocol
 | 
			
		||||
	if strings.Contains(url, "localhost") || strings.Contains(url, "127.0.0.1") {
 | 
			
		||||
		url = strings.ReplaceAll(url, "localhost", dt.API())
 | 
			
		||||
		url = strings.ReplaceAll(url, "127.0.0.1", dt.API())
 | 
			
		||||
		r := regexp.MustCompile("(:[0-9]+)")
 | 
			
		||||
		t := r.FindString(url)
 | 
			
		||||
		if t != "" {
 | 
			
		||||
			url = strings.Replace(url, t, ":8080", -1)
 | 
			
		||||
		}
 | 
			
		||||
		r.ReplaceAllString(url, ":8080")
 | 
			
		||||
	}
 | 
			
		||||
	return url
 | 
			
		||||
func (p *PeerCache) urlFormat(hostUrl string, dt tools.DataType) string {
 | 
			
		||||
	return hostUrl + "/" + strings.ReplaceAll(dt.API(), "oc-", "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkPeerStatus checks the status of a peer
 | 
			
		||||
func (p *PeerCache) checkPeerStatus(peerID string, appName string, caller *tools.HTTPCaller) (*Peer, bool) {
 | 
			
		||||
func (p *PeerCache) checkPeerStatus(peerID string, appName string) (*Peer, bool) {
 | 
			
		||||
	api := tools.API{}
 | 
			
		||||
	access := (&Peer{}).GetAccessor(nil)
 | 
			
		||||
	access := NewShallowAccessor()
 | 
			
		||||
	res, code, _ := access.LoadOne(peerID) // Load the peer from db
 | 
			
		||||
	if code != 200 {                       // no peer no party
 | 
			
		||||
		return nil, false
 | 
			
		||||
	}
 | 
			
		||||
	methods := caller.URLS[utils.PEER.String()] // Get the methods url of the peer
 | 
			
		||||
	if methods == nil {
 | 
			
		||||
		return res.(*Peer), false
 | 
			
		||||
	}
 | 
			
		||||
	meth := methods[tools.POST] // Get the POST method to check status
 | 
			
		||||
	if meth == "" {
 | 
			
		||||
		return res.(*Peer), false
 | 
			
		||||
	}
 | 
			
		||||
	url := p.urlFormat(res.(*Peer).Url+meth, utils.PEER)              // Format the URL
 | 
			
		||||
	state, services := api.CheckRemotePeer(url)                       // Check the status of the peer
 | 
			
		||||
	res.(*Peer).Services = services                                   // Update the services states of the peer
 | 
			
		||||
	url := p.urlFormat(res.(*Peer).Url, tools.PEER) + "/status" // Format the URL
 | 
			
		||||
	state, services := api.CheckRemotePeer(url)
 | 
			
		||||
	res.(*Peer).ServicesState = services                              // Update the services states of the peer
 | 
			
		||||
	access.UpdateOne(res, peerID)                                     // Update the peer in the db
 | 
			
		||||
	return res.(*Peer), state != tools.DEAD && services[appName] == 0 // Return the peer and its status
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LaunchPeerExecution launches an execution on a peer
 | 
			
		||||
// The method contacts the path described by : peer.Url + datatype path (from enums) + replacement of id by dataID
 | 
			
		||||
func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
 | 
			
		||||
	dt utils.DataType, method tools.METHOD, body map[string]interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) {
 | 
			
		||||
	methods := caller.URLS[dt.String()] // Get the methods url of the data type
 | 
			
		||||
	if _, ok := methods[method]; !ok {
 | 
			
		||||
		return nil, errors.New("no path found")
 | 
			
		||||
	}
 | 
			
		||||
	meth := methods[method] // Get the method url to execute
 | 
			
		||||
	if meth == "" {
 | 
			
		||||
		return nil, errors.New("no path found")
 | 
			
		||||
	} else {
 | 
			
		||||
		meth = strings.ReplaceAll(meth, ":id", dataID) // Replace the id in the url in case of a DELETE / UPDATE method (it's a standard naming in OC)
 | 
			
		||||
	dt tools.DataType, method tools.METHOD, body interface{}, caller tools.HTTPCallerITF) (map[string]interface{}, error) {
 | 
			
		||||
	fmt.Println("Launching peer execution on", caller.GetUrls(), dt, method)
 | 
			
		||||
	methods := caller.GetUrls()[dt] // Get the methods url of the data type
 | 
			
		||||
	if m, ok := methods[method]; !ok || m == "" {
 | 
			
		||||
		return map[string]interface{}{}, errors.New("Requested method " + method.String() + " not declared in HTTPCaller")
 | 
			
		||||
	}
 | 
			
		||||
	path := methods[method]                        // Get the path corresponding to the action we want to execute
 | 
			
		||||
	path = strings.ReplaceAll(path, ":id", dataID) // Replace the id in the path in case of a DELETE / UPDATE method (it's a standard naming in OC)
 | 
			
		||||
	url := ""
 | 
			
		||||
 | 
			
		||||
	// Check the status of the peer
 | 
			
		||||
	if mypeer, ok := p.checkPeerStatus(peerID, dt.API(), caller); !ok {
 | 
			
		||||
	if mypeer, ok := p.checkPeerStatus(peerID, dt.API()); !ok && mypeer != nil {
 | 
			
		||||
		// If the peer is not reachable, add the execution to the failed executions list
 | 
			
		||||
		pexec := &PeerExecution{
 | 
			
		||||
			Method:   method.String(),
 | 
			
		||||
			Url:      p.urlFormat((mypeer.Url)+meth, dt),
 | 
			
		||||
			Url:      p.urlFormat((mypeer.Url), dt) + path, // the url is constitued of : host URL + resource path + action path (ex : mypeer.com/datacenter/resourcetype/path/to/action)
 | 
			
		||||
			Body:     body,
 | 
			
		||||
			DataType: dt.EnumIndex(),
 | 
			
		||||
			DataID:   dataID,
 | 
			
		||||
		}
 | 
			
		||||
		mypeer.AddExecution(*pexec)
 | 
			
		||||
		mypeer.GetAccessor(nil).UpdateOne(mypeer, peerID) // Update the peer in the db
 | 
			
		||||
		return nil, errors.New("peer is not reachable")
 | 
			
		||||
		NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
 | 
			
		||||
		return map[string]interface{}{}, errors.New("peer is " + peerID + " not reachable")
 | 
			
		||||
	} else {
 | 
			
		||||
		if mypeer == nil {
 | 
			
		||||
			return map[string]interface{}{}, errors.New("peer " + peerID + " not found")
 | 
			
		||||
		}
 | 
			
		||||
		// If the peer is reachable, launch the execution
 | 
			
		||||
		url = p.urlFormat((mypeer.Url)+meth, dt)          // Format the URL
 | 
			
		||||
		url = p.urlFormat((mypeer.Url), dt) + path     // Format the URL
 | 
			
		||||
		tmp := mypeer.FailedExecution                  // Get the failed executions list
 | 
			
		||||
		mypeer.FailedExecution = []PeerExecution{}     // Reset the failed executions list
 | 
			
		||||
		mypeer.GetAccessor(nil).UpdateOne(mypeer, peerID) // Update the peer in the db
 | 
			
		||||
		NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
 | 
			
		||||
		for _, v := range tmp {                        // Retry the failed executions
 | 
			
		||||
			go p.exec(v.Url, tools.ToMethod(v.Method), v.Body, caller)
 | 
			
		||||
			go p.Exec(v.Url, tools.ToMethod(v.Method), v.Body, caller)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, p.exec(url, method, body, caller) // Execute the method
 | 
			
		||||
	return p.Exec(url, method, body, caller) // Execute the method
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// exec executes the method on the peer
 | 
			
		||||
func (p *PeerCache) exec(url string, method tools.METHOD, body map[string]interface{}, caller *tools.HTTPCaller) error {
 | 
			
		||||
func (p *PeerCache) Exec(url string, method tools.METHOD, body interface{}, caller tools.HTTPCallerITF) (map[string]interface{}, error) {
 | 
			
		||||
	var b []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	if method == tools.POST { // Execute the POST method if it's a POST method
 | 
			
		||||
@@ -123,13 +103,15 @@ func (p *PeerCache) exec(url string, method tools.METHOD, body map[string]interf
 | 
			
		||||
		b, err = caller.CallDelete(url, "")
 | 
			
		||||
	}
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return m, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e, ok := m["error"]; !ok && e != "" { // Check if there is an error in the response
 | 
			
		||||
		return errors.New(fmt.Sprintf("%v", m["error"]))
 | 
			
		||||
	err = json.Unmarshal(b, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return m, err
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
	if e, ok := m["error"]; ok && e != "<nil>" && e != "" { // Check if there is an error in the response
 | 
			
		||||
		return m, errors.New(fmt.Sprintf("%v", m["error"]))
 | 
			
		||||
	}
 | 
			
		||||
	return m, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,43 @@
 | 
			
		||||
package peer
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type peerMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
	OverrideAuth           bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the peerMongoAccessor
 | 
			
		||||
func New() *peerMongoAccessor {
 | 
			
		||||
	return &peerMongoAccessor{}
 | 
			
		||||
func NewShallowAccessor() *peerMongoAccessor {
 | 
			
		||||
	return &peerMongoAccessor{
 | 
			
		||||
		OverrideAuth: true,
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
 | 
			
		||||
			Type:   tools.PEER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *peerMongoAccessor {
 | 
			
		||||
	return &peerMongoAccessor{
 | 
			
		||||
		OverrideAuth: false,
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.PEER,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) ShouldVerifyAuth() bool {
 | 
			
		||||
	return !wfa.OverrideAuth
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -20,70 +45,55 @@ func New() *peerMongoAccessor {
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericDeleteOne(id, wfa)
 | 
			
		||||
	return utils.GenericDeleteOne(id, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericUpdateOne(set.(*Peer), id, wfa, &Peer{})
 | 
			
		||||
	return utils.GenericUpdateOne(set.(*Peer), id, wfa, &Peer{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data.(*Peer), wfa)
 | 
			
		||||
	return utils.GenericStoreOne(data.(*Peer), wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
	return utils.GenericStoreOne(data, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var peer Peer
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&peer)
 | 
			
		||||
 | 
			
		||||
	return &peer, 200, nil
 | 
			
		||||
func (dca *peerMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Peer](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa peerMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []Peer
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
func (wfa *peerMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Peer](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Peer](filters, search, wfa.GetDefaultFilter(search),
 | 
			
		||||
		func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			return d
 | 
			
		||||
		}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters {
 | 
			
		||||
	if i, err := strconv.Atoi(search); err == nil {
 | 
			
		||||
		return &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // search by name if no filters are provided
 | 
			
		||||
				"state": {{Operator: dbs.EQUAL.String(), Value: i}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if search == "*" {
 | 
			
		||||
			search = ""
 | 
			
		||||
		}
 | 
			
		||||
		return &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // search by name if no filters are provided
 | 
			
		||||
				"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"url":                 {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []Peer
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								models/peer/tests/peer_cache_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								models/peer/tests/peer_cache_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
package peer_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MockHTTPCaller mocks tools.HTTPCaller
 | 
			
		||||
type MockHTTPCaller struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	URLS map[tools.DataType]map[tools.METHOD]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *MockHTTPCaller) GetUrls() map[tools.DataType]map[tools.METHOD]string {
 | 
			
		||||
	return c.URLS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockHTTPCaller) CallPost(url, token string, body interface{}, types ...string) ([]byte, error) {
 | 
			
		||||
	args := m.Called(url, token, body)
 | 
			
		||||
	return args.Get(0).([]byte), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockHTTPCaller) CallGet(url, token string, types ...string) ([]byte, error) {
 | 
			
		||||
	args := m.Called(url, token)
 | 
			
		||||
	return args.Get(0).([]byte), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockHTTPCaller) CallDelete(url, token string) ([]byte, error) {
 | 
			
		||||
	args := m.Called(url, token)
 | 
			
		||||
	return args.Get(0).([]byte), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLaunchPeerExecution_PeerNotReachable(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{
 | 
			
		||||
		URLS: map[tools.DataType]map[tools.METHOD]string{
 | 
			
		||||
			tools.PEER: {
 | 
			
		||||
				tools.POST: "/execute/:id",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	exec, err := cache.LaunchPeerExecution("peer-id", "data-id", tools.PEER, tools.POST, map[string]string{"a": "b"}, caller)
 | 
			
		||||
	assert.Nil(t, exec)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "not reachable")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecSuccess(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{}
 | 
			
		||||
	url := "http://mockpeer/resource"
 | 
			
		||||
	response := map[string]interface{}{"result": "ok"}
 | 
			
		||||
	data, _ := json.Marshal(response)
 | 
			
		||||
 | 
			
		||||
	caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
 | 
			
		||||
	_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	caller.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecReturnsErrorField(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{}
 | 
			
		||||
	url := "http://mockpeer/resource"
 | 
			
		||||
	response := map[string]interface{}{"error": "something failed"}
 | 
			
		||||
	data, _ := json.Marshal(response)
 | 
			
		||||
 | 
			
		||||
	caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
 | 
			
		||||
	_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, "something failed", err.Error())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecInvalidJSON(t *testing.T) {
 | 
			
		||||
	cache := &peer.PeerCache{}
 | 
			
		||||
	caller := &MockHTTPCaller{}
 | 
			
		||||
	url := "http://mockpeer/resource"
 | 
			
		||||
	caller.On("CallPost", url, "", mock.Anything).Return([]byte("{invalid json}"), nil)
 | 
			
		||||
	_, err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "invalid character")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mockAccessor struct {
 | 
			
		||||
	loadOne   func(string) (interface{}, int, error)
 | 
			
		||||
	updateOne func(interface{}, string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockAccessor) LoadOne(id string) (interface{}, int, error) {
 | 
			
		||||
	return m.loadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockAccessor) UpdateOne(i interface{}, id string) error {
 | 
			
		||||
	return m.updateOne(i, id)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								models/peer/tests/peer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								models/peer/tests/peer_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
package peer_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockAccessor struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	utils.AbstractAccessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(set, id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(filters, search, isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTestPeer() *peer.Peer {
 | 
			
		||||
	return &peer.Peer{
 | 
			
		||||
		Url:           "http://localhost",
 | 
			
		||||
		WalletAddress: "0x123",
 | 
			
		||||
		PublicKey:     "pubkey",
 | 
			
		||||
		State:         peer.SELF,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	mockAcc.On("DeleteOne", "id").Return(newTestPeer(), 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.DeleteOne("id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.NotNil(t, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpdateOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	peerObj := newTestPeer()
 | 
			
		||||
	mockAcc.On("UpdateOne", peerObj, "id").Return(peerObj, 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.UpdateOne(peerObj, "id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, peerObj, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	peerObj := newTestPeer()
 | 
			
		||||
	mockAcc.On("StoreOne", peerObj).Return(peerObj, 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.StoreOne(peerObj)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, peerObj, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOne_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	mockAcc.On("LoadOne", "test-id").Return(newTestPeer(), 200, nil)
 | 
			
		||||
 | 
			
		||||
	obj, code, err := mockAcc.LoadOne("test-id")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.NotNil(t, obj)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadAll_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	expected := []utils.ShallowDBObject{newTestPeer()}
 | 
			
		||||
	mockAcc.On("LoadAll", false).Return(expected, 200, nil)
 | 
			
		||||
 | 
			
		||||
	objs, code, err := mockAcc.LoadAll(false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, expected, objs)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSearch_UsingMock(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
	filters := &dbs.Filters{}
 | 
			
		||||
	expected := []utils.ShallowDBObject{newTestPeer()}
 | 
			
		||||
	mockAcc.On("Search", filters, "test", false).Return(expected, 200, nil)
 | 
			
		||||
 | 
			
		||||
	objs, code, err := mockAcc.Search(filters, "test", false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 200, code)
 | 
			
		||||
	assert.Equal(t, expected, objs)
 | 
			
		||||
	mockAcc.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,131 +0,0 @@
 | 
			
		||||
package resource_model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* AbstractResource is a struct that represents a resource
 | 
			
		||||
* it defines the resource data
 | 
			
		||||
 */
 | 
			
		||||
type AbstractResource struct {
 | 
			
		||||
	utils.AbstractObject                // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	ShortDescription     string         `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
 | 
			
		||||
	Description          string         `json:"description,omitempty" bson:"description,omitempty"`                                 // Description is the description of the resource
 | 
			
		||||
	Logo                 string         `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"`                           // Logo is the logo of the resource
 | 
			
		||||
	Owner                string         `json:"owner,omitempty" bson:"owner,omitempty" validate:"required"`                         // Owner is the owner of the resource
 | 
			
		||||
	OwnerLogo            string         `json:"owner_logo,omitempty" bson:"owner_logo,omitempty"`                                   // OwnerLogo is the owner logo of the resource
 | 
			
		||||
	SourceUrl            string         `json:"source_url,omitempty" bson:"source_url,omitempty" validate:"required"`               // SourceUrl is the source URL of the resource
 | 
			
		||||
	PeerID               string         `json:"peer_id,omitempty" bson:"peer_id,omitempty" validate:"required"`                     // PeerID is the ID of the peer getting this resource
 | 
			
		||||
	Price                string         `json:"price,omitempty" bson:"price,omitempty"`                                             // Price is the price of access to the resource
 | 
			
		||||
	License              string         `json:"license,omitempty" bson:"license,omitempty"`                                         // License is the license of the resource
 | 
			
		||||
	ResourceModel        *ResourceModel `json:"resource_model,omitempty" bson:"resource_model,omitempty"`                           // ResourceModel is the model of the resource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* GetModelValue returns the value of the model key
 | 
			
		||||
 */
 | 
			
		||||
func (abs *AbstractResource) GetModelValue(key string) interface{} {
 | 
			
		||||
	if abs.ResourceModel == nil || abs.ResourceModel.Model == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := abs.ResourceModel.Model[key]; !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return abs.ResourceModel.Model[key].Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* GetModelType returns the type of the model key
 | 
			
		||||
 */
 | 
			
		||||
func (abs *AbstractResource) GetModelType(key string) interface{} {
 | 
			
		||||
	if abs.ResourceModel == nil || abs.ResourceModel.Model == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := abs.ResourceModel.Model[key]; !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return abs.ResourceModel.Model[key].Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* GetModelKeys returns the keys of the model
 | 
			
		||||
 */
 | 
			
		||||
func (abs *AbstractResource) GetModelKeys() []string {
 | 
			
		||||
	keys := make([]string, 0)
 | 
			
		||||
	for k := range abs.ResourceModel.Model {
 | 
			
		||||
		keys = append(keys, k)
 | 
			
		||||
	}
 | 
			
		||||
	return keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* GetModelReadOnly returns the readonly of the model key
 | 
			
		||||
 */
 | 
			
		||||
func (abs *AbstractResource) GetModelReadOnly(key string) interface{} {
 | 
			
		||||
	if abs.ResourceModel == nil || abs.ResourceModel.Model == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := abs.ResourceModel.Model[key]; !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return abs.ResourceModel.Model[key].ReadOnly
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Model struct {
 | 
			
		||||
	Type     string      `json:"type,omitempty" bson:"type,omitempty"`         // Type is the type of the model
 | 
			
		||||
	Value    interface{} `json:"value,omitempty" bson:"value,omitempty"`       // Value is the value of the model
 | 
			
		||||
	ReadOnly bool        `json:"readonly,omitempty" bson:"readonly,omitempty"` // ReadOnly is the readonly of the model
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* ResourceModel is a struct that represents a resource model
 | 
			
		||||
* it defines the resource metadata and specificity
 | 
			
		||||
* Warning: This struct is not user available, it is only used by the system
 | 
			
		||||
 */
 | 
			
		||||
type ResourceModel struct {
 | 
			
		||||
	UUID         string           `json:"id,omitempty" bson:"id,omitempty" validate:"required"`
 | 
			
		||||
	ResourceType string           `json:"resource_type,omitempty" bson:"resource_type,omitempty" validate:"required"`
 | 
			
		||||
	Model        map[string]Model `json:"model,omitempty" bson:"model,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *ResourceModel) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceModel) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ResourceModel) GetName() string {
 | 
			
		||||
	return d.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ResourceModel) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := &ResourceModelMongoAccessor{}
 | 
			
		||||
	data.Init(utils.RESOURCE_MODEL, caller)
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *ResourceModel) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *ResourceModel) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
@@ -1,83 +0,0 @@
 | 
			
		||||
package resource_model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceModelMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceModelMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericDeleteOne(id, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceModelMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericUpdateOne(set, id, wfa, &ResourceModel{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceModelMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceModelMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceModelMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var workflow ResourceModel
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&workflow)
 | 
			
		||||
	return &workflow, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa ResourceModelMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []ResourceModel
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceModelMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{
 | 
			
		||||
				"resource_type": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []ResourceModel
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										203
									
								
								models/resources/compute.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										203
									
								
								models/resources/compute.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,203 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* ComputeResource is a struct that represents a compute resource
 | 
			
		||||
* it defines the resource compute
 | 
			
		||||
 */
 | 
			
		||||
type ComputeResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*ComputeResourceInstance]
 | 
			
		||||
	Architecture   string                  `json:"architecture,omitempty" bson:"architecture,omitempty"` // Architecture is the architecture
 | 
			
		||||
	Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"`    // Infrastructure is the infrastructure
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ComputeResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*ComputeResource](tools.COMPUTE_RESOURCE, request, func() utils.DBObject { return &ComputeResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ComputeResource) GetType() string {
 | 
			
		||||
	return tools.COMPUTE_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	if t != tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
 | 
			
		||||
	priced := p.(*PricedResource)
 | 
			
		||||
	return &PricedComputeResource{
 | 
			
		||||
		PricedResource: *priced,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeNode struct {
 | 
			
		||||
	Name     string           `json:"name,omitempty" bson:"name,omitempty"`
 | 
			
		||||
	Quantity int64            `json:"quantity" bson:"quantity" default:"1"`
 | 
			
		||||
	RAM      *models.RAM      `bson:"ram,omitempty" json:"ram,omitempty"`   // RAM is the RAM
 | 
			
		||||
	CPUs     map[string]int64 `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs     map[string]int64 `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeResourceInstance struct {
 | 
			
		||||
	ResourceInstance[*ComputeResourcePartnership]
 | 
			
		||||
	Source             string                 `json:"source,omitempty" bson:"source,omitempty"` // Source is the source of the resource
 | 
			
		||||
	SecurityLevel      string                 `json:"security_level,omitempty" bson:"security_level,omitempty"`
 | 
			
		||||
	PowerSources       []string               `json:"power_sources,omitempty" bson:"power_sources,omitempty"`
 | 
			
		||||
	AnnualCO2Emissions float64                `json:"annual_co2_emissions,omitempty" bson:"co2_emissions,omitempty"`
 | 
			
		||||
	CPUs               map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs               map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
	Nodes              []*ComputeNode         `json:"nodes,omitempty" bson:"nodes,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeResourcePartnership struct {
 | 
			
		||||
	ResourcePartnerShip[*ComputeResourcePricingProfile]
 | 
			
		||||
	MinGaranteedCPUsCores    map[string]float64 `json:"garanteed_cpus,omitempty" bson:"garanteed_cpus,omitempty"`
 | 
			
		||||
	MinGaranteedGPUsMemoryGB map[string]float64 `json:"garanteed_gpus,omitempty" bson:"garanteed_gpus,omitempty"`
 | 
			
		||||
	MinGaranteedRAMSize      float64            `json:"garanteed_ram,omitempty" bson:"garanteed_ram,omitempty"`
 | 
			
		||||
 | 
			
		||||
	MaxAllowedCPUsCores    map[string]float64 `json:"allowed_cpus,omitempty" bson:"allowed_cpus,omitempty"`
 | 
			
		||||
	MaxAllowedGPUsMemoryGB map[string]float64 `json:"allowed_gpus,omitempty" bson:"allowed_gpus,omitempty"`
 | 
			
		||||
	MaxAllowedRAMSize      float64            `json:"allowed_ram,omitempty" bson:"allowed_ram,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComputeResourcePricingProfile struct {
 | 
			
		||||
	pricing.ExploitPricingProfile[pricing.TimePricingStrategy]
 | 
			
		||||
	// ExploitPricingProfile is the pricing profile of a compute it means that we exploit the resource for an amount of continuous time
 | 
			
		||||
	CPUsPrices map[string]float64 `json:"cpus_prices,omitempty" bson:"cpus_prices,omitempty"` // CPUsPrices is the prices of the CPUs
 | 
			
		||||
	GPUsPrices map[string]float64 `json:"gpus_prices,omitempty" bson:"gpus_prices,omitempty"` // GPUsPrices is the prices of the GPUs
 | 
			
		||||
	RAMPrice   float64            `json:"ram_price" bson:"ram_price" default:"-1"`            // RAMPrice is the price of the RAM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	if p.Pricing.BuyingStrategy == pricing.PERMANENT {
 | 
			
		||||
		p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ComputeResourcePricingProfile) GetOverrideStrategyValue() int {
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOT A PROPER QUANTITY
 | 
			
		||||
// amountOfData is the number of CPUs, GPUs or RAM dependings on the params
 | 
			
		||||
func (p *ComputeResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	if len(params) < 1 {
 | 
			
		||||
		return 0, errors.New("params must be set")
 | 
			
		||||
	}
 | 
			
		||||
	pp := float64(0)
 | 
			
		||||
	model := params[1]
 | 
			
		||||
	if strings.Contains(params[0], "cpus") && len(params) > 1 {
 | 
			
		||||
		if _, ok := p.CPUsPrices[model]; ok {
 | 
			
		||||
			p.Pricing.Price = p.CPUsPrices[model]
 | 
			
		||||
		}
 | 
			
		||||
		r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		pp += r
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Contains(params[0], "gpus") && len(params) > 1 {
 | 
			
		||||
		if _, ok := p.GPUsPrices[model]; ok {
 | 
			
		||||
			p.Pricing.Price = p.GPUsPrices[model]
 | 
			
		||||
		}
 | 
			
		||||
		r, err := p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		pp += r
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Contains(params[0], "ram") {
 | 
			
		||||
		if p.RAMPrice >= 0 {
 | 
			
		||||
			p.Pricing.Price = p.RAMPrice
 | 
			
		||||
		}
 | 
			
		||||
		r, err := p.Pricing.GetPrice(float64(amountOfData), explicitDuration, start, &end)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		pp += r
 | 
			
		||||
	}
 | 
			
		||||
	return pp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedComputeResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
 | 
			
		||||
	CPUsLocated map[string]float64 `json:"cpus_in_use" bson:"cpus_in_use"` // CPUsInUse is the list of CPUs in use
 | 
			
		||||
	GPUsLocated map[string]float64 `json:"gpus_in_use" bson:"gpus_in_use"` // GPUsInUse is the list of GPUs in use
 | 
			
		||||
	RAMLocated  float64            `json:"ram_in_use" bson:"ram_in_use"`   // RAMInUse is the RAM in use
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedComputeResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.COMPUTE_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedComputeResource) GetPrice() (float64, error) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	price := float64(0)
 | 
			
		||||
	for _, l := range []map[string]float64{r.CPUsLocated, r.GPUsLocated} {
 | 
			
		||||
		for model, amountOfData := range l {
 | 
			
		||||
			cpus, err := pricing.GetPrice(float64(amountOfData), r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "cpus", model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			price += cpus
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ram, err := pricing.GetPrice(r.RAMLocated, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd, "ram")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	price += ram
 | 
			
		||||
	return price, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* FillWithDefaultProcessingUsage fills the order item with the default processing usage
 | 
			
		||||
* it depends on the processing usage only if nothing is set, during order
 | 
			
		||||
 */
 | 
			
		||||
func (i *PricedComputeResource) FillWithDefaultProcessingUsage(usage *ProcessingUsage) {
 | 
			
		||||
	for _, cpu := range usage.CPUs {
 | 
			
		||||
		if _, ok := i.CPUsLocated[cpu.Model]; !ok {
 | 
			
		||||
			i.CPUsLocated[cpu.Model] = 0
 | 
			
		||||
		}
 | 
			
		||||
		if i.CPUsLocated[cpu.Model] < float64(cpu.Cores) {
 | 
			
		||||
			i.CPUsLocated[cpu.Model] = float64(cpu.Cores)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cpu := range usage.GPUs {
 | 
			
		||||
		i.GPUsLocated[cpu.Model] = 1
 | 
			
		||||
	}
 | 
			
		||||
	i.RAMLocated = usage.RAM.SizeGb
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										188
									
								
								models/resources/data.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										188
									
								
								models/resources/data.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* DataResource is a struct that represents a data resource
 | 
			
		||||
* it defines the resource data
 | 
			
		||||
 */
 | 
			
		||||
type DataResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*DataInstance]
 | 
			
		||||
	Type                   string     `bson:"type,omitempty" json:"type,omitempty"`
 | 
			
		||||
	Quality                string     `bson:"quality,omitempty" json:"quality,omitempty"`
 | 
			
		||||
	OpenData               bool       `bson:"open_data" json:"open_data" default:"false"` // Type is the type of the storage
 | 
			
		||||
	Static                 bool       `bson:"static" json:"static" default:"false"`
 | 
			
		||||
	UpdatePeriod           *time.Time `bson:"update_period,omitempty" json:"update_period,omitempty"`
 | 
			
		||||
	PersonalData           bool       `bson:"personal_data,omitempty" json:"personal_data,omitempty"`
 | 
			
		||||
	AnonymizedPersonalData bool       `bson:"anonymized_personal_data,omitempty" json:"anonymized_personal_data,omitempty"`
 | 
			
		||||
	SizeGB                 float64    `json:"size,omitempty" bson:"size,omitempty"` // SizeGB is the size of the data	License              DataLicense `json:"license" bson:"license" description:"license of the data" default:"0"` // License is the license of the data
 | 
			
		||||
	// ? Interest               DataLicense `json:"interest" bson:"interest" description:"interest of the data" default:"0"`      // Interest is the interest of the data
 | 
			
		||||
	Example string `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DataResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*DataResource](tools.DATA_RESOURCE, request, func() utils.DBObject { return &DataResource{} }) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DataResource) GetType() string {
 | 
			
		||||
	return tools.DATA_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *DataResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	if t != tools.DATA_RESOURCE {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
 | 
			
		||||
	priced := p.(*PricedResource)
 | 
			
		||||
	return &PricedDataResource{
 | 
			
		||||
		PricedResource: *priced,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataInstance struct {
 | 
			
		||||
	ResourceInstance[*DataResourcePartnership]
 | 
			
		||||
	Source string `json:"source,omitempty" bson:"source,omitempty"` // Source is the source of the data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *DataInstance) StoreDraftDefault() {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, p := range ri.ResourceInstance.Env {
 | 
			
		||||
		if p.Attr == "source" {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		ri.ResourceInstance.Env = append(ri.ResourceInstance.Env, models.Param{
 | 
			
		||||
			Attr:     "source",
 | 
			
		||||
			Value:    ri.Source,
 | 
			
		||||
			Readonly: true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	ri.ResourceInstance.StoreDraftDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataResourcePartnership struct {
 | 
			
		||||
	ResourcePartnerShip[*DataResourcePricingProfile]
 | 
			
		||||
	MaxDownloadableGbAllowed      float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
 | 
			
		||||
	PersonalDataAllowed           bool    `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
 | 
			
		||||
	AnonymizedPersonalDataAllowed bool    `json:"anonymized_personal_data_allowed,omitempty" bson:"anonymized_personal_data_allowed,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataResourcePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PER_DOWNLOAD DataResourcePricingStrategy = iota + 6
 | 
			
		||||
	PER_TB_DOWNLOADED
 | 
			
		||||
	PER_GB_DOWNLOADED
 | 
			
		||||
	PER_MB_DOWNLOADED
 | 
			
		||||
	PER_KB_DOWNLOADED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) String() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DOWNLOAD", "PER TB DOWNLOADED", "PER GB DOWNLOADED", "PER MB DOWNLOADED", "PER KB DOWNLOADED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DataResourcePricingStrategyList() []DataResourcePricingStrategy {
 | 
			
		||||
	return []DataResourcePricingStrategy{PER_DOWNLOAD, PER_TB_DOWNLOADED, PER_GB_DOWNLOADED, PER_MB_DOWNLOADED, PER_KB_DOWNLOADED}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToDataResourcePricingStrategy(i int) DataResourcePricingStrategy {
 | 
			
		||||
	return DataResourcePricingStrategy(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) GetStrategy() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) GetStrategyValue() int {
 | 
			
		||||
	return int(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t DataResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case PER_DOWNLOAD:
 | 
			
		||||
		return 1, nil
 | 
			
		||||
	case PER_TB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB * 1000, nil
 | 
			
		||||
	case PER_GB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB, nil
 | 
			
		||||
	case PER_MB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB / 1000, nil
 | 
			
		||||
	case PER_KB_DOWNLOADED:
 | 
			
		||||
		return amountOfDataGB / 1000000, nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("pricing strategy not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DataResourcePricingProfile struct {
 | 
			
		||||
	pricing.AccessPricingProfile[DataResourcePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) GetOverrideStrategyValue() int {
 | 
			
		||||
	return p.Pricing.OverrideStrategy.GetStrategyValue()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDuration float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DataResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	// TODO WHAT ABOUT PAY PER USE... it's a complicate CASE
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.PERMANENT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedDataResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
	UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedDataResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.DATA_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedDataResource) GetPrice() (float64, error) {
 | 
			
		||||
	fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	var err error
 | 
			
		||||
	amountOfData := float64(1)
 | 
			
		||||
	if pricing.GetOverrideStrategyValue() >= 0 {
 | 
			
		||||
		amountOfData, err = ToDataResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
package data
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* DataResource is a struct that represents a data resource
 | 
			
		||||
* it defines the resource data
 | 
			
		||||
 */
 | 
			
		||||
type DataResource struct {
 | 
			
		||||
	resource_model.AbstractResource          // AbstractResource contains the basic fields of an object (id, name)
 | 
			
		||||
	Protocols                       []string `json:"protocol,omitempty" bson:"protocol,omitempty"`                                 //TODO Enum type
 | 
			
		||||
	DataType                        string   `json:"datatype,omitempty" bson:"datatype,omitempty"`                                 // DataType is the type of the data
 | 
			
		||||
	Example                         string   `json:"example,omitempty" bson:"example,omitempty" description:"base64 encoded data"` // Example is an example of the data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *DataResource) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *DataResource) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DataResource) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                          // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.DATA_RESOURCE, caller) // Initialize the accessor with the DATA_RESOURCE model type
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
@@ -1,110 +0,0 @@
 | 
			
		||||
package data
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	mongo "cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type dataMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the dataMongoAccessor
 | 
			
		||||
func New() *dataMongoAccessor {
 | 
			
		||||
	return &dataMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (dma *dataMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return dma.GenericDeleteOne(id, dma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *dataMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set.(*DataResource).ResourceModel = nil
 | 
			
		||||
	return dma.GenericUpdateOne(set, id, dma, &DataResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *dataMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data.(*DataResource).ResourceModel = nil
 | 
			
		||||
	return dma.GenericStoreOne(data, dma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *dataMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return dma.GenericStoreOne(data, dma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *dataMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var data DataResource
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, dma.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		dma.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&data)
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, dma.GetType())
 | 
			
		||||
	if err == nil && len(resources) > 0 {
 | 
			
		||||
		data.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
	}
 | 
			
		||||
	return &data, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa dataMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []DataResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *dataMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
				"abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.short_description":   {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.owner":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.source_url":          {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []DataResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
package data
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStoreOneData(t *testing.T) {
 | 
			
		||||
	d := DataResource{DataType: "jpeg", Example: "123456",
 | 
			
		||||
		AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testData"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dma := New()
 | 
			
		||||
	id, _, _ := dma.StoreOne(&d)
 | 
			
		||||
 | 
			
		||||
	assert.NotEmpty(t, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOneDate(t *testing.T) {
 | 
			
		||||
	d := DataResource{DataType: "jpeg", Example: "123456",
 | 
			
		||||
		AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testData"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dma := New()
 | 
			
		||||
	new_d, _, _ := dma.StoreOne(&d)
 | 
			
		||||
	assert.Equal(t, d, new_d)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
package datacenter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* DatacenterResource is a struct that represents a datacenter resource
 | 
			
		||||
* it defines the resource datacenter
 | 
			
		||||
 */
 | 
			
		||||
type DatacenterResource struct {
 | 
			
		||||
	resource_model.AbstractResource
 | 
			
		||||
	CPUs []*CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs
 | 
			
		||||
	RAM  *RAM   `bson:"ram,omitempty" json:"ram,omitempty"`   // RAM is the RAM
 | 
			
		||||
	GPUs []*GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *DatacenterResource) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *DatacenterResource) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DatacenterResource) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()
 | 
			
		||||
	data.Init(utils.DATACENTER_RESOURCE, caller)
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CPU is a struct that represents a CPU
 | 
			
		||||
type CPU struct {
 | 
			
		||||
	Cores         uint   `bson:"cores,omitempty" json:"cores,omitempty"`               //TODO: validate
 | 
			
		||||
	Architecture  string `bson:"architecture,omitempty" json:"architecture,omitempty"` //TOOD: enum
 | 
			
		||||
	Shared        bool   `bson:"shared,omitempty" json:"shared,omitempty"`
 | 
			
		||||
	MinimumMemory uint   `bson:"minimum_memory,omitempty" json:"minimum_memory,omitempty"`
 | 
			
		||||
	Platform      string `bson:"platform,omitempty" json:"platform,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RAM struct {
 | 
			
		||||
	Size uint `bson:"size,omitempty" json:"size,omitempty" description:"Units in MB"`
 | 
			
		||||
	Ecc  bool `bson:"ecc,omitempty" json:"ecc,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GPU struct {
 | 
			
		||||
	CudaCores   uint   `bson:"cuda_cores,omitempty" json:"cuda_cores,omitempty"`
 | 
			
		||||
	Model       string `bson:"model,omitempty" json:"model,omitempty"`
 | 
			
		||||
	Memory      uint   `bson:"memory,omitempty" json:"memory,omitempty" description:"Units in MB"`
 | 
			
		||||
	TensorCores uint   `bson:"tensor_cores,omitempty" json:"tensor_cores,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
@@ -1,112 +0,0 @@
 | 
			
		||||
package datacenter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type datacenterMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the datacenterMongoAccessor
 | 
			
		||||
func New() *datacenterMongoAccessor {
 | 
			
		||||
	return &datacenterMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (dca *datacenterMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return dca.GenericDeleteOne(id, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *datacenterMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set.(*DatacenterResource).ResourceModel = nil
 | 
			
		||||
	return dca.GenericUpdateOne(set, id, dca, &DatacenterResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *datacenterMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data.(*DatacenterResource).ResourceModel = nil
 | 
			
		||||
	return dca.GenericStoreOne(data, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *datacenterMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return dca.GenericStoreOne(data, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *datacenterMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var datacenter DatacenterResource
 | 
			
		||||
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, dca.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		dca.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res_mongo.Decode(&datacenter)
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, dca.GetType())
 | 
			
		||||
	if err == nil && len(resources) > 0 {
 | 
			
		||||
		datacenter.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
	}
 | 
			
		||||
	return &datacenter, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa datacenterMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []DatacenterResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *datacenterMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
				"abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.short_description":   {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.owner":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.source_url":          {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []DatacenterResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
package datacenter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStoreOneDatacenter(t *testing.T) {
 | 
			
		||||
	dc := DatacenterResource{
 | 
			
		||||
		AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testDatacenter"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dcma := New()
 | 
			
		||||
	id, _, _ := dcma.StoreOne(&dc)
 | 
			
		||||
 | 
			
		||||
	assert.NotEmpty(t, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOneDatacenter(t *testing.T) {
 | 
			
		||||
	dc := DatacenterResource{
 | 
			
		||||
		AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testDatacenter"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dcma := New()
 | 
			
		||||
	new_dc, _, _ := dcma.StoreOne(&dc)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, dc, new_dc)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								models/resources/interfaces.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										38
									
								
								models/resources/interfaces.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceInterface interface {
 | 
			
		||||
	utils.DBObject
 | 
			
		||||
	Trim()
 | 
			
		||||
	ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF
 | 
			
		||||
	GetType() string
 | 
			
		||||
	GetSelectedInstance() ResourceInstanceITF
 | 
			
		||||
	ClearEnv() utils.DBObject
 | 
			
		||||
	SetAllowedInstances(request *tools.APIRequest)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourceInstanceITF interface {
 | 
			
		||||
	utils.DBObject
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetName() string
 | 
			
		||||
	StoreDraftDefault()
 | 
			
		||||
	ClearEnv()
 | 
			
		||||
	GetProfile() pricing.PricingProfileITF
 | 
			
		||||
	GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
 | 
			
		||||
	GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
 | 
			
		||||
	ClearPeerGroups()
 | 
			
		||||
	GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF
 | 
			
		||||
	GetPartnerships(peerID string, groups []string) []ResourcePartnerITF
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourcePartnerITF interface {
 | 
			
		||||
	GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
 | 
			
		||||
	GetPeerGroups() map[string][]string
 | 
			
		||||
	ClearPeerGroups()
 | 
			
		||||
	GetProfile(buying int, strategy int) pricing.PricingProfileITF
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								models/resources/models.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										65
									
								
								models/resources/models.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceSet struct {
 | 
			
		||||
	Datas       []string `bson:"datas,omitempty" json:"datas,omitempty"`
 | 
			
		||||
	Storages    []string `bson:"storages,omitempty" json:"storages,omitempty"`
 | 
			
		||||
	Processings []string `bson:"processings,omitempty" json:"processings,omitempty"`
 | 
			
		||||
	Computes    []string `bson:"computes,omitempty" json:"computes,omitempty"`
 | 
			
		||||
	Workflows   []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
 | 
			
		||||
 | 
			
		||||
	DataResources       []*DataResource       `bson:"-" json:"data_resources,omitempty"`
 | 
			
		||||
	StorageResources    []*StorageResource    `bson:"-" json:"storage_resources,omitempty"`
 | 
			
		||||
	ProcessingResources []*ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
 | 
			
		||||
	ComputeResources    []*ComputeResource    `bson:"-" json:"compute_resources,omitempty"`
 | 
			
		||||
	WorkflowResources   []*WorkflowResource   `bson:"-" json:"workflow_resources,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceSet) Clear() {
 | 
			
		||||
	r.DataResources = nil
 | 
			
		||||
	r.StorageResources = nil
 | 
			
		||||
	r.ProcessingResources = nil
 | 
			
		||||
	r.ComputeResources = nil
 | 
			
		||||
	r.WorkflowResources = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceSet) Fill(request *tools.APIRequest) {
 | 
			
		||||
	r.Clear()
 | 
			
		||||
	for k, v := range map[utils.DBObject][]string{
 | 
			
		||||
		(&DataResource{}):       r.Datas,
 | 
			
		||||
		(&ComputeResource{}):    r.Computes,
 | 
			
		||||
		(&StorageResource{}):    r.Storages,
 | 
			
		||||
		(&ProcessingResource{}): r.Processings,
 | 
			
		||||
		(&WorkflowResource{}):   r.Workflows,
 | 
			
		||||
	} {
 | 
			
		||||
		for _, id := range v {
 | 
			
		||||
			d, _, e := k.GetAccessor(request).LoadOne(id)
 | 
			
		||||
			if e == nil {
 | 
			
		||||
				switch k.(type) {
 | 
			
		||||
				case *DataResource:
 | 
			
		||||
					r.DataResources = append(r.DataResources, d.(*DataResource))
 | 
			
		||||
				case *ComputeResource:
 | 
			
		||||
					r.ComputeResources = append(r.ComputeResources, d.(*ComputeResource))
 | 
			
		||||
				case *StorageResource:
 | 
			
		||||
					r.StorageResources = append(r.StorageResources, d.(*StorageResource))
 | 
			
		||||
				case *ProcessingResource:
 | 
			
		||||
					r.ProcessingResources = append(r.ProcessingResources, d.(*ProcessingResource))
 | 
			
		||||
				case *WorkflowResource:
 | 
			
		||||
					r.WorkflowResources = append(r.WorkflowResources, d.(*WorkflowResource))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ItemResource struct {
 | 
			
		||||
	Data       *DataResource       `bson:"data,omitempty" json:"data,omitempty"`
 | 
			
		||||
	Processing *ProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"`
 | 
			
		||||
	Storage    *StorageResource    `bson:"storage,omitempty" json:"storage,omitempty"`
 | 
			
		||||
	Compute    *ComputeResource    `bson:"compute,omitempty" json:"compute,omitempty"`
 | 
			
		||||
	Workflow   *WorkflowResource   `bson:"workflow,omitempty" json:"workflow,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								models/resources/priced_resource.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										100
									
								
								models/resources/priced_resource.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PricedResource struct {
 | 
			
		||||
	Name                     string                    `json:"name,omitempty" bson:"name,omitempty"`
 | 
			
		||||
	Logo                     string                    `json:"logo,omitempty" bson:"logo,omitempty"`
 | 
			
		||||
	InstancesRefs            map[string]string         `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
 | 
			
		||||
	SelectedPricing          pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
 | 
			
		||||
	ExplicitBookingDurationS float64                   `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"`
 | 
			
		||||
	UsageStart               *time.Time                `json:"start,omitempty" bson:"start,omitempty"`
 | 
			
		||||
	UsageEnd                 *time.Time                `json:"end,omitempty" bson:"end,omitempty"`
 | 
			
		||||
	CreatorID                string                    `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
 | 
			
		||||
	ResourceID               string                    `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
 | 
			
		||||
	ResourceType             tools.DataType            `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) SelectPricing() pricing.PricingProfileITF {
 | 
			
		||||
	return abs.SelectedPricing
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetID() string {
 | 
			
		||||
	return abs.ResourceID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetType() tools.DataType {
 | 
			
		||||
	return abs.ResourceType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetCreatorID() string {
 | 
			
		||||
	return abs.CreatorID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) IsPurchasable() bool {
 | 
			
		||||
	if abs.SelectedPricing == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return (abs.SelectedPricing).IsPurchasable()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) IsBooked() bool {
 | 
			
		||||
	if abs.SelectedPricing == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return (abs.SelectedPricing).IsBooked()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetLocationEnd() *time.Time {
 | 
			
		||||
	return abs.UsageEnd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetLocationStart() *time.Time {
 | 
			
		||||
	return abs.UsageStart
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) SetLocationStart(start time.Time) {
 | 
			
		||||
	abs.UsageStart = &start
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) SetLocationEnd(end time.Time) {
 | 
			
		||||
	abs.UsageEnd = &end
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *PricedResource) GetExplicitDurationInS() float64 {
 | 
			
		||||
	if abs.ExplicitBookingDurationS == 0 {
 | 
			
		||||
		if abs.UsageEnd == nil && abs.UsageStart == nil {
 | 
			
		||||
			return time.Duration(1 * time.Hour).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		if abs.UsageEnd == nil {
 | 
			
		||||
			add := abs.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
			abs.UsageEnd = &add
 | 
			
		||||
		}
 | 
			
		||||
		return abs.UsageEnd.Sub(*abs.UsageStart).Seconds()
 | 
			
		||||
	}
 | 
			
		||||
	return abs.ExplicitBookingDurationS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedResource) GetPrice() (float64, error) {
 | 
			
		||||
	fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	return pricing.GetPrice(1, 0, *r.UsageStart, *r.UsageEnd)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								models/resources/processing.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										94
									
								
								models/resources/processing.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ProcessingUsage struct {
 | 
			
		||||
	CPUs map[string]*models.CPU `bson:"cpus,omitempty" json:"cpus,omitempty"` // CPUs is the list of CPUs key is model
 | 
			
		||||
	GPUs map[string]*models.GPU `bson:"gpus,omitempty" json:"gpus,omitempty"` // GPUs is the list of GPUs key is model
 | 
			
		||||
	RAM  *models.RAM            `bson:"ram,omitempty" json:"ram,omitempty"`   // RAM is the RAM
 | 
			
		||||
 | 
			
		||||
	StorageGb    float64 `bson:"storage,omitempty" json:"storage,omitempty"` // Storage is the storage
 | 
			
		||||
	Hypothesis   string  `bson:"hypothesis,omitempty" json:"hypothesis,omitempty"`
 | 
			
		||||
	ScalingModel string  `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* ProcessingResource is a struct that represents a processing resource
 | 
			
		||||
* it defines the resource processing
 | 
			
		||||
 */
 | 
			
		||||
type ProcessingResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*ProcessingInstance]
 | 
			
		||||
	Infrastructure enum.InfrastructureType `json:"infrastructure" bson:"infrastructure" default:"-1"` // Infrastructure is the infrastructure
 | 
			
		||||
	IsService      bool                    `json:"is_service,omitempty" bson:"is_service,omitempty"`  // IsService is a flag that indicates if the processing is a service
 | 
			
		||||
	Usage          *ProcessingUsage        `bson:"usage,omitempty" json:"usage,omitempty"`            // Usage is the usage of the processing
 | 
			
		||||
	OpenSource     bool                    `json:"open_source" bson:"open_source" default:"false"`
 | 
			
		||||
	License        string                  `json:"license,omitempty" bson:"license,omitempty"`
 | 
			
		||||
	Maturity       string                  `json:"maturity,omitempty" bson:"maturity,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ProcessingResource) GetType() string {
 | 
			
		||||
	return tools.PROCESSING_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProcessingResourceAccess struct {
 | 
			
		||||
	Container *models.Container `json:"container,omitempty" bson:"container,omitempty"` // Container is the container
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProcessingInstance struct {
 | 
			
		||||
	ResourceInstance[*ResourcePartnerShip[*ProcessingResourcePricingProfile]]
 | 
			
		||||
	Access *ProcessingResourceAccess `json:"access,omitempty" bson:"access,omitempty"` // Access is the access
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedProcessingResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
	IsService bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedProcessingResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.PROCESSING_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PricedProcessingResource) GetExplicitDurationInS() float64 {
 | 
			
		||||
	if a.ExplicitBookingDurationS == 0 {
 | 
			
		||||
		if a.IsService || a.UsageStart == nil {
 | 
			
		||||
			if a.IsService {
 | 
			
		||||
				return -1
 | 
			
		||||
			}
 | 
			
		||||
			return time.Duration(1 * time.Hour).Seconds()
 | 
			
		||||
		}
 | 
			
		||||
		return a.UsageEnd.Sub(*a.UsageStart).Seconds()
 | 
			
		||||
	}
 | 
			
		||||
	return a.ExplicitBookingDurationS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ProcessingResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*ProcessingResource](tools.PROCESSING_RESOURCE, request, func() utils.DBObject { return &ProcessingResource{} }) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProcessingResourcePricingProfile struct {
 | 
			
		||||
	pricing.AccessPricingProfile[pricing.TimePricingStrategy] // AccessPricingProfile is the pricing profile of a data it means that we can access the data for an amount of time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.PERMANENT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	return p.Pricing.GetPrice(amountOfData, val, start, &end)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
package processing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* ProcessingResource is a struct that represents a processing resource
 | 
			
		||||
* it defines the resource processing
 | 
			
		||||
 */
 | 
			
		||||
type ProcessingResource struct {
 | 
			
		||||
	resource_model.AbstractResource
 | 
			
		||||
	CPUs         []*datacenter.CPU `bson:"cpus,omitempty" json:"cp_us,omitempty"`                  // CPUs is the list of CPUs
 | 
			
		||||
	GPUs         []*datacenter.GPU `bson:"gpus,omitempty" json:"gp_us,omitempty"`                  // GPUs is the list of GPUs
 | 
			
		||||
	RAM          *datacenter.RAM   `bson:"ram,omitempty" json:"ram,omitempty"`                     // RAM is the RAM
 | 
			
		||||
	Storage      uint              `bson:"storage,omitempty" json:"storage,omitempty"`             // Storage is the storage
 | 
			
		||||
	Parallel     bool              `bson:"parallel,omitempty" json:"parallel,omitempty"`           // Parallel is a flag that indicates if the processing is parallel
 | 
			
		||||
	ScalingModel uint              `bson:"scaling_model,omitempty" json:"scaling_model,omitempty"` // ScalingModel is the scaling model
 | 
			
		||||
	DiskIO       string            `bson:"disk_io,omitempty" json:"disk_io,omitempty"`             // DiskIO is the disk IO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *ProcessingResource) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *ProcessingResource) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *ProcessingResource) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                                // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.PROCESSING_RESOURCE, caller) // Initialize the accessor with the PROCESSING_RESOURCE model type
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
@@ -1,114 +0,0 @@
 | 
			
		||||
package processing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type processingMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the processingMongoAccessor
 | 
			
		||||
func New() *processingMongoAccessor {
 | 
			
		||||
	return &processingMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (pma *processingMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return pma.GenericDeleteOne(id, pma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pma *processingMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set.(*ProcessingResource).ResourceModel = nil
 | 
			
		||||
	return pma.GenericUpdateOne(set, id, pma, &ProcessingResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pma *processingMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data.(*ProcessingResource).ResourceModel = nil
 | 
			
		||||
	return pma.GenericStoreOne(data, pma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pma *processingMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return pma.GenericStoreOne(data, pma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pma *processingMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
 | 
			
		||||
	var processing ProcessingResource
 | 
			
		||||
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, pma.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		pma.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res_mongo.Decode(&processing)
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, pma.GetType())
 | 
			
		||||
	if err == nil && len(resources) > 0 {
 | 
			
		||||
		processing.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
	}
 | 
			
		||||
	return &processing, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa processingMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []ProcessingResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Search searches for processing resources in the database, given some filters OR a search string
 | 
			
		||||
func (wfa *processingMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
				"abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.short_description":   {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.owner":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.source_url":          {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []ProcessingResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
package processing
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func TestStoreOneProcessing(t *testing.T) {
 | 
			
		||||
	p := ProcessingResource{Container: "totoCont",
 | 
			
		||||
		AbstractResource: resources.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testData"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sma := ProcessingMongoAccessor{}
 | 
			
		||||
	id, _, _ := sma.StoreOne(&p)
 | 
			
		||||
 | 
			
		||||
	assert.NotEmpty(t, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOneProcessing(t *testing.T) {
 | 
			
		||||
	p := ProcessingResource{Container: "totoCont",
 | 
			
		||||
		AbstractResource: resources.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testData"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sma := ProcessingMongoAccessor{}
 | 
			
		||||
	new_s, _, _ := sma.StoreOne(&p)
 | 
			
		||||
	assert.Equal(t, p, new_s)
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
							
								
								
									
										34
									
								
								models/resources/purchase_resource/purchase_resource.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								models/resources/purchase_resource/purchase_resource.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
package purchase_resource
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PurchaseResource struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	DestPeerID   string
 | 
			
		||||
	PricedItem   pricing.PricedItemITF `json:"priced_item,omitempty" bson:"priced_item,omitempty" validate:"required"`
 | 
			
		||||
	ExecutionsID string                `json:"executions_id,omitempty" bson:"executions_id,omitempty" validate:"required"` // ExecutionsID is the ID of the executions
 | 
			
		||||
	EndDate      *time.Time            `json:"end_buying_date,omitempty" bson:"end_buying_date,omitempty"`
 | 
			
		||||
	ResourceID   string                `json:"resource_id" bson:"resource_id" validate:"required"`
 | 
			
		||||
	ResourceType tools.DataType        `json:"resource_type" bson:"resource_type" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *PurchaseResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PurchaseResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	return r.IsDraft, set // only draft buying can be updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PurchaseResource) CanDelete() bool { // ENDBuyingDate is passed
 | 
			
		||||
	if r.EndDate != nil {
 | 
			
		||||
		return time.Now().UTC().After(*r.EndDate)
 | 
			
		||||
	}
 | 
			
		||||
	return false // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
package purchase_resource
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PurchaseResourceMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the bookingMongoAccessor
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *PurchaseResourceMongoAccessor {
 | 
			
		||||
	return &PurchaseResourceMongoAccessor{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(tools.PURCHASE_RESOURCE.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    tools.PURCHASE_RESOURCE,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, a, &PurchaseResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*PurchaseResource](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
 | 
			
		||||
			utils.GenericDeleteOne(id, a)
 | 
			
		||||
			return nil, 404, nil
 | 
			
		||||
		}
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*PurchaseResource](a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*PurchaseResource](filters, search, (&PurchaseResource{}).GetObjectFilters(search), a.getExec(), isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *PurchaseResourceMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
	return func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		if d.(*PurchaseResource).EndDate != nil && time.Now().UTC().After(*d.(*PurchaseResource).EndDate) {
 | 
			
		||||
			utils.GenericDeleteOne(d.GetID(), a)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return d
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package purchase_resource_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	res := &purchase_resource.PurchaseResource{}
 | 
			
		||||
	accessor := res.GetAccessor(req)
 | 
			
		||||
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
	assert.Equal(t, tools.PURCHASE_RESOURCE, accessor.(*purchase_resource.PurchaseResourceMongoAccessor).Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate(t *testing.T) {
 | 
			
		||||
	set := &purchase_resource.PurchaseResource{ResourceID: "id"}
 | 
			
		||||
	r := &purchase_resource.PurchaseResource{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{IsDraft: true},
 | 
			
		||||
	}
 | 
			
		||||
	can, updated := r.CanUpdate(set)
 | 
			
		||||
	assert.True(t, can)
 | 
			
		||||
	assert.Equal(t, set, updated)
 | 
			
		||||
 | 
			
		||||
	r.IsDraft = false
 | 
			
		||||
	can, _ = r.CanUpdate(set)
 | 
			
		||||
	assert.False(t, can)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanDelete(t *testing.T) {
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	past := now.Add(-1 * time.Hour)
 | 
			
		||||
	future := now.Add(1 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	t.Run("nil EndDate", func(t *testing.T) {
 | 
			
		||||
		r := &purchase_resource.PurchaseResource{}
 | 
			
		||||
		assert.False(t, r.CanDelete())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("EndDate in past", func(t *testing.T) {
 | 
			
		||||
		r := &purchase_resource.PurchaseResource{EndDate: &past}
 | 
			
		||||
		assert.True(t, r.CanDelete())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("EndDate in future", func(t *testing.T) {
 | 
			
		||||
		r := &purchase_resource.PurchaseResource{EndDate: &future}
 | 
			
		||||
		assert.False(t, r.CanDelete())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										304
									
								
								models/resources/resource.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										304
									
								
								models/resources/resource.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,58 +1,266 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/data"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/processing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/storage"
 | 
			
		||||
	w "cloud.o-forge.io/core/oc-lib/models/resources/workflow"
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/config"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/biter777/countries"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AbstractResource is the struct containing all of the attributes commons to all ressources
 | 
			
		||||
 | 
			
		||||
// Resource is the interface to be implemented by all classes inheriting from Resource to have the same behavior
 | 
			
		||||
 | 
			
		||||
// http://www.inanzzz.com/index.php/post/wqbs/a-basic-usage-of-int-and-string-enum-types-in-golang
 | 
			
		||||
type ResourceSet struct {
 | 
			
		||||
	Datas       []string `bson:"datas,omitempty" json:"datas,omitempty"`
 | 
			
		||||
	Storages    []string `bson:"storages,omitempty" json:"storages,omitempty"`
 | 
			
		||||
	Processings []string `bson:"processings,omitempty" json:"processings,omitempty"`
 | 
			
		||||
	Datacenters []string `bson:"datacenters,omitempty" json:"datacenters,omitempty"`
 | 
			
		||||
	Workflows   []string `bson:"workflows,omitempty" json:"workflows,omitempty"`
 | 
			
		||||
 | 
			
		||||
	DataResources       []*data.DataResource             `bson:"-" json:"data_resources,omitempty"`
 | 
			
		||||
	StorageResources    []*storage.StorageResource       `bson:"-" json:"storage_resources,omitempty"`
 | 
			
		||||
	ProcessingResources []*processing.ProcessingResource `bson:"-" json:"processing_resources,omitempty"`
 | 
			
		||||
	DatacenterResources []*datacenter.DatacenterResource `bson:"-" json:"datacenter_resources,omitempty"`
 | 
			
		||||
	WorkflowResources   []*w.WorkflowResource            `bson:"-" json:"workflow_resources,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ItemResource struct {
 | 
			
		||||
	Data       *data.DataResource             `bson:"data,omitempty" json:"data,omitempty"`
 | 
			
		||||
	Processing *processing.ProcessingResource `bson:"processing,omitempty" json:"processing,omitempty"`
 | 
			
		||||
	Storage    *storage.StorageResource       `bson:"storage,omitempty" json:"storage,omitempty"`
 | 
			
		||||
	Datacenter *datacenter.DatacenterResource `bson:"datacenter,omitempty" json:"datacenter,omitempty"`
 | 
			
		||||
	Workflow   *w.WorkflowResource            `bson:"workflow,omitempty" json:"workflow,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *ItemResource) GetAbstractRessource() *resource_model.AbstractResource {
 | 
			
		||||
	
 | 
			
		||||
	if(i.Data != nil){
 | 
			
		||||
		return &i.Data.AbstractResource
 | 
			
		||||
	}
 | 
			
		||||
	if(i.Processing != nil){
 | 
			
		||||
		return &i.Processing.AbstractResource
 | 
			
		||||
	}
 | 
			
		||||
	if(i.Storage != nil){
 | 
			
		||||
		return &i.Storage.AbstractResource
 | 
			
		||||
	}
 | 
			
		||||
	if(i.Datacenter != nil){
 | 
			
		||||
		return &i.Datacenter.AbstractResource
 | 
			
		||||
	}
 | 
			
		||||
	if(i.Workflow != nil){
 | 
			
		||||
		return &i.Workflow.AbstractResource
 | 
			
		||||
type AbstractResource struct {
 | 
			
		||||
	utils.AbstractObject                // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	Type                  string        `json:"type,omitempty" bson:"type,omitempty"`                                               // Type is the type of the resource
 | 
			
		||||
	Logo                  string        `json:"logo,omitempty" bson:"logo,omitempty" validate:"required"`                           // Logo is the logo of the resource
 | 
			
		||||
	Description           string        `json:"description,omitempty" bson:"description,omitempty"`                                 // Description is the description of the resource
 | 
			
		||||
	ShortDescription      string        `json:"short_description,omitempty" bson:"short_description,omitempty" validate:"required"` // ShortDescription is the short description of the resource
 | 
			
		||||
	Owners                []utils.Owner `json:"owners,omitempty" bson:"owners,omitempty"`                                           // Owners is the list of owners of the resource
 | 
			
		||||
	UsageRestrictions     string        `bson:"usage_restrictions,omitempty" json:"usage_restrictions,omitempty"`
 | 
			
		||||
	SelectedInstanceIndex *int          `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) GetSelectedInstance() ResourceInstanceITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) GetType() string {
 | 
			
		||||
	return tools.INVALID.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	if r.IsDraft != set.IsDrafted() && set.IsDrafted() {
 | 
			
		||||
		return true, set // only state can be updated
 | 
			
		||||
	}
 | 
			
		||||
	return r.IsDraft != set.IsDrafted() && set.IsDrafted(), set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractResource) CanDelete() bool {
 | 
			
		||||
	return r.IsDraft // only draft bookings can be deleted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
 | 
			
		||||
	AbstractResource     // AbstractResource contains the basic fields of an object (id, name)
 | 
			
		||||
	Instances        []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource            // Bill is the bill of the resource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	instances := map[string]string{}
 | 
			
		||||
	profiles := []pricing.PricingProfileITF{}
 | 
			
		||||
	for _, instance := range abs.Instances {
 | 
			
		||||
		instances[instance.GetID()] = instance.GetName()
 | 
			
		||||
		profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
 | 
			
		||||
	}
 | 
			
		||||
	var profile pricing.PricingProfileITF
 | 
			
		||||
	if t := abs.GetSelectedInstance(); t != nil {
 | 
			
		||||
		profile = t.GetProfile()
 | 
			
		||||
	}
 | 
			
		||||
	if profile == nil && len(profiles) > 0 {
 | 
			
		||||
		profile = profiles[0]
 | 
			
		||||
	}
 | 
			
		||||
	return &PricedResource{
 | 
			
		||||
		Name:            abs.Name,
 | 
			
		||||
		Logo:            abs.Logo,
 | 
			
		||||
		ResourceID:      abs.UUID,
 | 
			
		||||
		ResourceType:    t,
 | 
			
		||||
		InstancesRefs:   instances,
 | 
			
		||||
		SelectedPricing: profile,
 | 
			
		||||
		CreatorID:       abs.CreatorID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
 | 
			
		||||
	for _, instance := range abs.Instances {
 | 
			
		||||
		instance.ClearEnv()
 | 
			
		||||
	}
 | 
			
		||||
	return abs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF {
 | 
			
		||||
	if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex {
 | 
			
		||||
		return r.Instances[*r.SelectedInstanceIndex]
 | 
			
		||||
	}
 | 
			
		||||
	if len(r.Instances) > 0 {
 | 
			
		||||
		return r.Instances[0]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) SetAllowedInstances(request *tools.APIRequest) {
 | 
			
		||||
	if request != nil && request.PeerID == abs.CreatorID && request.PeerID != "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	abs.Instances = VerifyAuthAction[T](abs.Instances, request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *AbstractInstanciatedResource[T]) Trim() {
 | 
			
		||||
	d.Type = d.GetType()
 | 
			
		||||
	if ok, _ := (&peer.Peer{AbstractObject: utils.AbstractObject{UUID: d.CreatorID}}).IsMySelf(); !ok {
 | 
			
		||||
		for _, instance := range d.Instances {
 | 
			
		||||
			instance.ClearPeerGroups()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *AbstractInstanciatedResource[T]) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return len(VerifyAuthAction[T](abs.Instances, request)) > 0 || abs.AbstractObject.VerifyAuth(request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func VerifyAuthAction[T ResourceInstanceITF](baseInstance []T, request *tools.APIRequest) []T {
 | 
			
		||||
	instances := []T{}
 | 
			
		||||
	for _, instance := range baseInstance {
 | 
			
		||||
		_, peerGroups := instance.GetPeerGroups()
 | 
			
		||||
		for _, peers := range peerGroups {
 | 
			
		||||
			if request == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if grps, ok := peers[request.PeerID]; ok || config.GetConfig().Whitelist {
 | 
			
		||||
				if (ok && slices.Contains(grps, "*")) || (!ok && config.GetConfig().Whitelist) {
 | 
			
		||||
					instances = append(instances, instance)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				for _, grp := range grps {
 | 
			
		||||
					if slices.Contains(request.Groups, grp) {
 | 
			
		||||
						instances = append(instances, instance)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return instances
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GeoPoint struct {
 | 
			
		||||
	Latitude  float64 `json:"latitude,omitempty" bson:"latitude,omitempty"`
 | 
			
		||||
	Longitude float64 `json:"longitude,omitempty" bson:"longitude,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourceInstance[T ResourcePartnerITF] struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	Location                 GeoPoint              `json:"location,omitempty" bson:"location,omitempty"`
 | 
			
		||||
	Country                  countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
 | 
			
		||||
	AccessProtocol           string                `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
 | 
			
		||||
	Env                      []models.Param        `json:"env,omitempty" bson:"env,omitempty"`
 | 
			
		||||
	Inputs                   []models.Param        `json:"inputs,omitempty" bson:"inputs,omitempty"`
 | 
			
		||||
	Outputs                  []models.Param        `json:"outputs,omitempty" bson:"outputs,omitempty"`
 | 
			
		||||
	SelectedPartnershipIndex int                   `json:"selected_partnership_index,omitempty" bson:"selected_partnership_index,omitempty"`
 | 
			
		||||
	SelectedBuyingStrategy   int                   `json:"selected_buying_strategy,omitempty" bson:"selected_buying_strategy,omitempty"`
 | 
			
		||||
	SelectedStrategy         int                   `json:"selected_strategy,omitempty" bson:"selected_strategy,omitempty"`
 | 
			
		||||
	Partnerships             []T                   `json:"partnerships,omitempty" bson:"partnerships,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) ClearEnv() {
 | 
			
		||||
	ri.Env = []models.Param{}
 | 
			
		||||
	ri.Inputs = []models.Param{}
 | 
			
		||||
	ri.Outputs = []models.Param{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF {
 | 
			
		||||
	if len(ri.Partnerships) > ri.SelectedPartnershipIndex {
 | 
			
		||||
		prts := ri.Partnerships[ri.SelectedPartnershipIndex]
 | 
			
		||||
		return prts.GetProfile(ri.SelectedBuyingStrategy, ri.SelectedBuyingStrategy)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetSelectedPartnership(peerID string, groups []string) ResourcePartnerITF {
 | 
			
		||||
	if len(ri.Partnerships) > ri.SelectedPartnershipIndex {
 | 
			
		||||
		return ri.Partnerships[ri.SelectedPartnershipIndex]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF {
 | 
			
		||||
	partners := []ResourcePartnerITF{}
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		if p.GetPeerGroups()[peerID] != nil {
 | 
			
		||||
			for _, g := range p.GetPeerGroups()[peerID] {
 | 
			
		||||
				if slices.Contains(groups, g) {
 | 
			
		||||
					partners = append(partners, p)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return partners
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
 | 
			
		||||
	pricings := []pricing.PricingProfileITF{}
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...)
 | 
			
		||||
	}
 | 
			
		||||
	return pricings
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) {
 | 
			
		||||
	groups := []map[string][]string{}
 | 
			
		||||
	partners := []ResourcePartnerITF{}
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		partners = append(partners, p)
 | 
			
		||||
		groups = append(groups, p.GetPeerGroups())
 | 
			
		||||
	}
 | 
			
		||||
	return partners, groups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourceInstance[T]) ClearPeerGroups() {
 | 
			
		||||
	for _, p := range ri.Partnerships {
 | 
			
		||||
		p.ClearPeerGroups()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResourcePartnerShip[T pricing.PricingProfileITF] struct {
 | 
			
		||||
	Namespace       string              `json:"namespace" bson:"namespace" default:"default-namespace"`
 | 
			
		||||
	PeerGroups      map[string][]string `json:"peer_groups,omitempty" bson:"peer_groups,omitempty"`
 | 
			
		||||
	PricingProfiles map[int]map[int]T   `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"`
 | 
			
		||||
	// to upgrade pricing profiles. to be a map BuyingStrategy, map of Strategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *ResourcePartnerShip[T]) GetProfile(buying int, strategy int) pricing.PricingProfileITF {
 | 
			
		||||
	if strat, ok := ri.PricingProfiles[buying]; ok {
 | 
			
		||||
		if profile, ok := strat[strategy]; ok {
 | 
			
		||||
			return profile
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Le pricing doit être selectionné lors d'un scheduling...
 | 
			
		||||
le type de paiement défini le type de stratégie de paiement
 | 
			
		||||
note : il faut rajouté - une notion de facturation
 | 
			
		||||
Une order est l'ensemble de la commande... un booking une réservation, une purchase un acte d'achat.
 | 
			
		||||
Une bill (facture) représente alors... l'emission d'une facture à un instant T en but d'être honorée, envoyée ... etc.
 | 
			
		||||
*/
 | 
			
		||||
func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
 | 
			
		||||
	profiles := []pricing.PricingProfileITF{}
 | 
			
		||||
	if ri.PeerGroups[peerID] == nil {
 | 
			
		||||
		return profiles
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range ri.PeerGroups[peerID] {
 | 
			
		||||
		if slices.Contains(groups, p) || slices.Contains(groups, "*") {
 | 
			
		||||
			for _, ri := range ri.PricingProfiles {
 | 
			
		||||
				for _, i := range ri {
 | 
			
		||||
					profiles = append(profiles, i)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return profiles
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return profiles
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rp *ResourcePartnerShip[T]) GetPeerGroups() map[string][]string {
 | 
			
		||||
	return rp.PeerGroups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rp *ResourcePartnerShip[T]) ClearPeerGroups() {
 | 
			
		||||
	rp.PeerGroups = map[string][]string{}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								models/resources/resource_accessor.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										106
									
								
								models/resources/resource_accessor.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"slices"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ResourceMongoAccessor[T ResourceInterface] struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
	generateData           func() utils.DBObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the computeMongoAccessor
 | 
			
		||||
func NewAccessor[T ResourceInterface](t tools.DataType, request *tools.APIRequest, g func() utils.DBObject) *ResourceMongoAccessor[T] {
 | 
			
		||||
	if !slices.Contains([]tools.DataType{
 | 
			
		||||
		tools.COMPUTE_RESOURCE, tools.STORAGE_RESOURCE,
 | 
			
		||||
		tools.PROCESSING_RESOURCE, tools.WORKFLOW_RESOURCE,
 | 
			
		||||
		tools.DATA_RESOURCE,
 | 
			
		||||
	}, t) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &ResourceMongoAccessor[T]{
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    t,
 | 
			
		||||
		},
 | 
			
		||||
		generateData: g,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericDeleteOne(id, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	if dca.GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil, 404, errors.New("can't update a non existing computing units resource not reported onto compute units catalog")
 | 
			
		||||
	}
 | 
			
		||||
	set.(T).Trim()
 | 
			
		||||
	return utils.GenericUpdateOne(set, id, dca, dca.generateData())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	if dca.GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil, 404, errors.New("can't create a non existing computing units resource not reported onto compute units catalog")
 | 
			
		||||
	}
 | 
			
		||||
	data.(T).Trim()
 | 
			
		||||
	return utils.GenericStoreOne(data, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	if dca.GetType() == tools.COMPUTE_RESOURCE {
 | 
			
		||||
		return nil, 404, errors.New("can't copy/publish a non existing computing units resource not reported onto compute units catalog")
 | 
			
		||||
	}
 | 
			
		||||
	return dca.StoreOne(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dca *ResourceMongoAccessor[T]) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[T](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		d.(T).SetAllowedInstances(dca.Request)
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, dca)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceMongoAccessor[T]) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
		d.(T).SetAllowedInstances(wfa.Request)
 | 
			
		||||
		return d
 | 
			
		||||
	}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *ResourceMongoAccessor[T]) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	if filters == nil && search == "*" {
 | 
			
		||||
		return utils.GenericLoadAll[T](func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			d.(T).SetAllowedInstances(wfa.Request)
 | 
			
		||||
			return d
 | 
			
		||||
		}, isDraft, wfa)
 | 
			
		||||
	}
 | 
			
		||||
	return utils.GenericSearch[T](filters, search, wfa.getResourceFilter(search),
 | 
			
		||||
		func(d utils.DBObject) utils.ShallowDBObject {
 | 
			
		||||
			d.(T).SetAllowedInstances(wfa.Request)
 | 
			
		||||
			return d
 | 
			
		||||
		}, isDraft, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *ResourceMongoAccessor[T]) getResourceFilter(search string) *dbs.Filters {
 | 
			
		||||
	return &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.abstractobject.name":       {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.type":                      {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.short_description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.description":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.owners.name":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			"abstractintanciatedresource.abstractresource.abstractobject.creator_id": {{Operator: dbs.EQUAL.String(), Value: search}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										208
									
								
								models/resources/storage.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										208
									
								
								models/resources/storage.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/enum"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* StorageResource is a struct that represents a storage resource
 | 
			
		||||
* it defines the resource storage
 | 
			
		||||
 */
 | 
			
		||||
type StorageResource struct {
 | 
			
		||||
	AbstractInstanciatedResource[*StorageResourceInstance]                  // AbstractResource contains the basic fields of an object (id, name)
 | 
			
		||||
	StorageType                                            enum.StorageType `bson:"storage_type" json:"storage_type" default:"-1"` // Type is the type of the storage
 | 
			
		||||
	Acronym                                                string           `bson:"acronym,omitempty" json:"acronym,omitempty"`    // Acronym is the acronym of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *StorageResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*StorageResource](tools.STORAGE_RESOURCE, request, func() utils.DBObject { return &StorageResource{} }) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *StorageResource) GetType() string {
 | 
			
		||||
	return tools.STORAGE_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	if t != tools.STORAGE_RESOURCE {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
 | 
			
		||||
	priced := p.(*PricedResource)
 | 
			
		||||
	return &PricedStorageResource{
 | 
			
		||||
		PricedResource: *priced,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourceInstance struct {
 | 
			
		||||
	ResourceInstance[*StorageResourcePartnership]
 | 
			
		||||
	Source        string           `bson:"source,omitempty" json:"source,omitempty"` // Source is the source of the storage
 | 
			
		||||
	Path          string           `bson:"path,omitempty" json:"path,omitempty"`     // Path is the store folders in the source
 | 
			
		||||
	Local         bool             `bson:"local" json:"local"`
 | 
			
		||||
	SecurityLevel string           `bson:"security_level,omitempty" json:"security_level,omitempty"`
 | 
			
		||||
	SizeType      enum.StorageSize `bson:"size_type" json:"size_type" default:"0"`           // SizeType is the type of the storage size
 | 
			
		||||
	SizeGB        int64            `bson:"size,omitempty" json:"size,omitempty"`             // Size is the size of the storage
 | 
			
		||||
	Encryption    bool             `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
 | 
			
		||||
	Redundancy    string           `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
 | 
			
		||||
	Throughput    string           `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *StorageResourceInstance) ClearEnv() {
 | 
			
		||||
	ri.Env = []models.Param{}
 | 
			
		||||
	ri.Inputs = []models.Param{}
 | 
			
		||||
	ri.Outputs = []models.Param{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ri *StorageResourceInstance) StoreDraftDefault() {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, p := range ri.ResourceInstance.Env {
 | 
			
		||||
		if p.Attr == "source" {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		ri.ResourceInstance.Env = append(ri.ResourceInstance.Env, models.Param{
 | 
			
		||||
			Attr:     "source",
 | 
			
		||||
			Value:    ri.Source,
 | 
			
		||||
			Readonly: true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	ri.ResourceInstance.StoreDraftDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourcePartnership struct {
 | 
			
		||||
	ResourcePartnerShip[*StorageResourcePricingProfile]
 | 
			
		||||
	MaxSizeGBAllowed     float64 `json:"allowed_gb,omitempty" bson:"allowed_gb,omitempty"`
 | 
			
		||||
	OnlyEncryptedAllowed bool    `json:"personal_data_allowed,omitempty" bson:"personal_data_allowed,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PrivilegeStoragePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	BASIC_STORAGE PrivilegeStoragePricingStrategy = iota
 | 
			
		||||
	GARANTED_ON_DELAY_STORAGE
 | 
			
		||||
	GARANTED_STORAGE
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PrivilegeStoragePricingStrategyList() []PrivilegeStoragePricingStrategy {
 | 
			
		||||
	return []PrivilegeStoragePricingStrategy{BASIC_STORAGE, GARANTED_ON_DELAY_STORAGE, GARANTED_STORAGE}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t PrivilegeStoragePricingStrategy) String() string {
 | 
			
		||||
	return [...]string{"NO MEMORY HOLDING", "KEEPED ON MEMORY GARANTED DURING DELAY", "KEEPED ON MEMORY GARANTED"}[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourcePricingStrategy int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PER_DATA_STORED StorageResourcePricingStrategy = iota + 6
 | 
			
		||||
	PER_TB_STORED
 | 
			
		||||
	PER_GB_STORED
 | 
			
		||||
	PER_MB_STORED
 | 
			
		||||
	PER_KB_STORED
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func StorageResourcePricingStrategyList() []StorageResourcePricingStrategy {
 | 
			
		||||
	return []StorageResourcePricingStrategy{PER_DATA_STORED, PER_TB_STORED, PER_GB_STORED, PER_MB_STORED, PER_KB_STORED}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) String() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) GetStrategy() string {
 | 
			
		||||
	l := pricing.TimePricingStrategyListStr()
 | 
			
		||||
	l = append(l, []string{"PER DATA STORED", "PER TB STORED", "PER GB STORED", "PER MB STORED", "PER KB STORED"}...)
 | 
			
		||||
	return l[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) GetStrategyValue() int {
 | 
			
		||||
	return int(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToStorageResourcePricingStrategy(i int) StorageResourcePricingStrategy {
 | 
			
		||||
	return StorageResourcePricingStrategy(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t StorageResourcePricingStrategy) GetQuantity(amountOfDataGB float64) (float64, error) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case PER_DATA_STORED:
 | 
			
		||||
		return amountOfDataGB, nil
 | 
			
		||||
	case PER_TB_STORED:
 | 
			
		||||
		return amountOfDataGB * 1000, nil
 | 
			
		||||
	case PER_GB_STORED:
 | 
			
		||||
		return amountOfDataGB, nil
 | 
			
		||||
	case PER_MB_STORED:
 | 
			
		||||
		return (amountOfDataGB * 1000), nil
 | 
			
		||||
	case PER_KB_STORED:
 | 
			
		||||
		return amountOfDataGB * 1000000, nil
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("pricing strategy not found")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StorageResourcePricingProfile struct {
 | 
			
		||||
	pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
 | 
			
		||||
	return p.Pricing.BuyingStrategy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) IsBooked() bool {
 | 
			
		||||
	if p.Pricing.BuyingStrategy == pricing.PERMANENT {
 | 
			
		||||
		p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *StorageResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
 | 
			
		||||
	return p.Pricing.GetPrice(amountOfData, val, start, &end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PricedStorageResource struct {
 | 
			
		||||
	PricedResource
 | 
			
		||||
	UsageStorageGB float64 `json:"storage_gb,omitempty" bson:"storage_gb,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedStorageResource) GetType() tools.DataType {
 | 
			
		||||
	return tools.STORAGE_RESOURCE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *PricedStorageResource) GetPrice() (float64, error) {
 | 
			
		||||
	fmt.Println("GetPrice", r.UsageStart, r.UsageEnd)
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if r.UsageStart == nil {
 | 
			
		||||
		r.UsageStart = &now
 | 
			
		||||
	}
 | 
			
		||||
	if r.UsageEnd == nil {
 | 
			
		||||
		add := r.UsageStart.Add(time.Duration(1 * time.Hour))
 | 
			
		||||
		r.UsageEnd = &add
 | 
			
		||||
	}
 | 
			
		||||
	if r.SelectedPricing == nil {
 | 
			
		||||
		return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID)
 | 
			
		||||
	}
 | 
			
		||||
	pricing := r.SelectedPricing
 | 
			
		||||
	var err error
 | 
			
		||||
	amountOfData := float64(1)
 | 
			
		||||
	if pricing.GetOverrideStrategyValue() >= 0 {
 | 
			
		||||
		amountOfData, err = ToStorageResourcePricingStrategy(pricing.GetOverrideStrategyValue()).GetQuantity(r.UsageStorageGB)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return pricing.GetPrice(amountOfData, r.ExplicitBookingDurationS, *r.UsageStart, *r.UsageEnd)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,55 +0,0 @@
 | 
			
		||||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type URL struct {
 | 
			
		||||
	Protocol string `bson:"protocol,omitempty" json:"protocol,omitempty"` // Protocol is the protocol of the URL
 | 
			
		||||
	Path     string `bson:"path,omitempty" json:"path,omitempty"`         // Path is the path of the URL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* StorageResource is a struct that represents a storage resource
 | 
			
		||||
* it defines the resource storage
 | 
			
		||||
 */
 | 
			
		||||
type StorageResource struct {
 | 
			
		||||
	resource_model.AbstractResource        // AbstractResource contains the basic fields of an object (id, name)
 | 
			
		||||
	Acronym                         string `bson:"acronym,omitempty" json:"acronym,omitempty"` // Acronym is the acronym of the storage
 | 
			
		||||
	Type                            string `bson:"type,omitempty" json:"type,omitempty"`       // Type is the type of the storage
 | 
			
		||||
	Size                            uint   `bson:"size,omitempty" json:"size,omitempty"`       // Size is the size of the storage
 | 
			
		||||
	Url                             *URL   `bson:"url,omitempty" json:"url,omitempty"`         // Will allow to select between several protocols
 | 
			
		||||
 | 
			
		||||
	Encryption bool   `bson:"encryption,omitempty" json:"encryption,omitempty"` // Encryption is a flag that indicates if the storage is encrypted
 | 
			
		||||
	Redundancy string `bson:"redundancy,omitempty" json:"redundancy,omitempty"` // Redundancy is the redundancy of the storage
 | 
			
		||||
	Throughput string `bson:"throughput,omitempty" json:"throughput,omitempty"` // Throughput is the throughput of the storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *StorageResource) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *StorageResource) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *StorageResource) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                             // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.STORAGE_RESOURCE, caller) // Initialize the accessor with the STORAGE_RESOURCE model type
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
@@ -1,114 +0,0 @@
 | 
			
		||||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type storageMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the storageMongoAccessor
 | 
			
		||||
func New() *storageMongoAccessor {
 | 
			
		||||
	return &storageMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Nothing special here, just the basic CRUD operations
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
func (sma *storageMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return sma.GenericDeleteOne(id, sma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sma *storageMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set.(*StorageResource).ResourceModel = nil
 | 
			
		||||
	return sma.GenericUpdateOne(set, id, sma, &StorageResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sma *storageMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data.(*StorageResource).ResourceModel = nil
 | 
			
		||||
	return sma.GenericStoreOne(data, sma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sma *storageMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return sma.GenericStoreOne(data, sma)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sma *storageMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
 | 
			
		||||
	var storage StorageResource
 | 
			
		||||
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, sma.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		sma.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res_mongo.Decode(&storage)
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, sma.GetType())
 | 
			
		||||
	if err == nil && len(resources) > 0 {
 | 
			
		||||
		storage.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
	}
 | 
			
		||||
	return &storage, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa storageMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []StorageResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Search searches for storage resources in the database, given some filters OR a search string
 | 
			
		||||
func (wfa *storageMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
				"abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.short_description":   {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.owner":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.source_url":          {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []StorageResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource) // only get the abstract resource !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStoreOneStorage(t *testing.T) {
 | 
			
		||||
	s := StorageResource{Size: 123, Url: &URL{Protocol: "http", Path: "azerty.fr"},
 | 
			
		||||
		AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testData"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sma := New()
 | 
			
		||||
	id, _, _ := sma.StoreOne(&s)
 | 
			
		||||
 | 
			
		||||
	assert.NotEmpty(t, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOneStorage(t *testing.T) {
 | 
			
		||||
	s := StorageResource{Size: 123, Url: &URL{Protocol: "http", Path: "azerty.fr"},
 | 
			
		||||
		AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: "testData"},
 | 
			
		||||
			Description:    "Lorem Ipsum",
 | 
			
		||||
			Logo:           "azerty.com",
 | 
			
		||||
			Owner:          "toto",
 | 
			
		||||
			OwnerLogo:      "totoLogo",
 | 
			
		||||
			SourceUrl:      "azerty.fr",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sma := New()
 | 
			
		||||
	new_s, _, _ := sma.StoreOne(&s)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, s, new_s)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								models/resources/tests/compute_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								models/resources/tests/compute_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestComputeResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &resources.ComputeResource{}
 | 
			
		||||
	assert.Equal(t, tools.COMPUTE_RESOURCE.String(), r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	cr := &resources.ComputeResource{}
 | 
			
		||||
	accessor := cr.GetAccessor(req)
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResource_ConvertToPricedResource(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	cr := &resources.ComputeResource{}
 | 
			
		||||
	cr.UUID = "comp123"
 | 
			
		||||
	cr.AbstractInstanciatedResource.UUID = cr.UUID
 | 
			
		||||
	result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req)
 | 
			
		||||
	assert.NotNil(t, result)
 | 
			
		||||
	assert.IsType(t, &resources.PricedComputeResource{}, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResourcePricingProfile_GetPrice_CPUs(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	profile := resources.ComputeResourcePricingProfile{
 | 
			
		||||
		CPUsPrices: map[string]float64{"Xeon": 2.0},
 | 
			
		||||
		ExploitPricingProfile: pricing.ExploitPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
			AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
				Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{Price: 1.0},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	price, err := profile.GetPrice(2, 3600, start, end, "cpus", "Xeon")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Greater(t, price, float64(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) {
 | 
			
		||||
	profile := resources.ComputeResourcePricingProfile{}
 | 
			
		||||
	_, err := profile.GetPrice(1, 3600, time.Now(), time.Now())
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, "params must be set", err.Error())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedComputeResource_GetPrice(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(1 * time.Hour)
 | 
			
		||||
	r := resources.PricedComputeResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID:               "comp456",
 | 
			
		||||
			UsageStart:               &start,
 | 
			
		||||
			UsageEnd:                 &end,
 | 
			
		||||
			ExplicitBookingDurationS: 3600,
 | 
			
		||||
		},
 | 
			
		||||
		CPUsLocated: map[string]float64{"Xeon": 2},
 | 
			
		||||
		GPUsLocated: map[string]float64{"Tesla": 1},
 | 
			
		||||
		RAMLocated:  4,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	price, err := r.GetPrice()
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Greater(t, price, float64(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedComputeResource_GetPrice_MissingProfile(t *testing.T) {
 | 
			
		||||
	r := resources.PricedComputeResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID: "comp789",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	_, err := r.GetPrice()
 | 
			
		||||
	require.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "pricing profile must be set")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedComputeResource_FillWithDefaultProcessingUsage(t *testing.T) {
 | 
			
		||||
	usage := &resources.ProcessingUsage{
 | 
			
		||||
		CPUs: map[string]*models.CPU{"t": {Model: "Xeon", Cores: 4}},
 | 
			
		||||
		GPUs: map[string]*models.GPU{"t1": {Model: "Tesla"}},
 | 
			
		||||
		RAM:  &models.RAM{SizeGb: 16},
 | 
			
		||||
	}
 | 
			
		||||
	r := &resources.PricedComputeResource{
 | 
			
		||||
		CPUsLocated: make(map[string]float64),
 | 
			
		||||
		GPUsLocated: make(map[string]float64),
 | 
			
		||||
		RAMLocated:  0,
 | 
			
		||||
	}
 | 
			
		||||
	r.FillWithDefaultProcessingUsage(usage)
 | 
			
		||||
	assert.Equal(t, float64(4), r.CPUsLocated["Xeon"])
 | 
			
		||||
	assert.Equal(t, float64(1), r.GPUsLocated["Tesla"])
 | 
			
		||||
	assert.Equal(t, float64(16), r.RAMLocated)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								models/resources/tests/data_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								models/resources/tests/data_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDataResource_GetType(t *testing.T) {
 | 
			
		||||
	d := &resources.DataResource{}
 | 
			
		||||
	assert.Equal(t, tools.DATA_RESOURCE.String(), d.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	acc := (&resources.DataResource{}).GetAccessor(req)
 | 
			
		||||
	assert.NotNil(t, acc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResource_ConvertToPricedResource(t *testing.T) {
 | 
			
		||||
	d := &resources.DataResource{}
 | 
			
		||||
	d.UUID = "123"
 | 
			
		||||
	res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.IsType(t, &resources.PricedDataResource{}, res)
 | 
			
		||||
 | 
			
		||||
	nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.Nil(t, nilRes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataInstance_StoreDraftDefault(t *testing.T) {
 | 
			
		||||
	di := &resources.DataInstance{
 | 
			
		||||
		Source: "test-src",
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*resources.DataResourcePartnership]{
 | 
			
		||||
			Env: []models.Param{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	di.StoreDraftDefault()
 | 
			
		||||
	assert.Len(t, di.ResourceInstance.Env, 1)
 | 
			
		||||
	assert.Equal(t, "source", di.ResourceInstance.Env[0].Attr)
 | 
			
		||||
	assert.Equal(t, "test-src", di.ResourceInstance.Env[0].Value)
 | 
			
		||||
 | 
			
		||||
	// Call again, should not duplicate
 | 
			
		||||
	di.StoreDraftDefault()
 | 
			
		||||
	assert.Len(t, di.ResourceInstance.Env, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResourcePricingStrategy_GetQuantity(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		strategy resources.DataResourcePricingStrategy
 | 
			
		||||
		input    float64
 | 
			
		||||
		expected float64
 | 
			
		||||
	}{
 | 
			
		||||
		{resources.PER_DOWNLOAD, 1, 1},
 | 
			
		||||
		{resources.PER_TB_DOWNLOADED, 1, 1000},
 | 
			
		||||
		{resources.PER_GB_DOWNLOADED, 2.5, 2.5},
 | 
			
		||||
		{resources.PER_MB_DOWNLOADED, 1, 0.001},
 | 
			
		||||
		{resources.PER_KB_DOWNLOADED, 1, 0.000001},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		q, err := tt.strategy.GetQuantity(tt.input)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.InDelta(t, tt.expected, q, 1e-9)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := resources.DataResourcePricingStrategy(999).GetQuantity(1)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDataResourcePricingProfile_IsPurchased(t *testing.T) {
 | 
			
		||||
	profile := &resources.DataResourcePricingProfile{}
 | 
			
		||||
	profile.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
 | 
			
		||||
	assert.True(t, profile.IsPurchasable())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedDataResource_GetPrice(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	later := now.Add(1 * time.Hour)
 | 
			
		||||
	mockPrice := 42.0
 | 
			
		||||
 | 
			
		||||
	pricingProfile := &resources.DataResourcePricingProfile{AccessPricingProfile: pricing.AccessPricingProfile[resources.DataResourcePricingStrategy]{
 | 
			
		||||
		Pricing: pricing.PricingStrategy[resources.DataResourcePricingStrategy]{Price: 42.0}},
 | 
			
		||||
	}
 | 
			
		||||
	pricingProfile.Pricing.OverrideStrategy = resources.PER_GB_DOWNLOADED
 | 
			
		||||
 | 
			
		||||
	r := &resources.PricedDataResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			UsageStart: &now,
 | 
			
		||||
			UsageEnd:   &later,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	price, err := r.GetPrice()
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, mockPrice, price)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedDataResource_GetPrice_NoProfiles(t *testing.T) {
 | 
			
		||||
	r := &resources.PricedDataResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID: "test-resource",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	_, err := r.GetPrice()
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "pricing profile must be set")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedDataResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &resources.PricedDataResource{}
 | 
			
		||||
	assert.Equal(t, tools.DATA_RESOURCE, r.GetType())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								models/resources/tests/priced_resource_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								models/resources/tests/priced_resource_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ---- Mock PricingProfile ----
 | 
			
		||||
 | 
			
		||||
type MockPricingProfile struct {
 | 
			
		||||
	pricing.PricingProfileITF
 | 
			
		||||
	Purchased  bool
 | 
			
		||||
	ReturnErr  bool
 | 
			
		||||
	ReturnCost float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPricingProfile) IsPurchasable() bool {
 | 
			
		||||
	return m.Purchased
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPricingProfile) GetPrice(amount float64, explicitDuration float64, start time.Time, end time.Time, _ ...string) (float64, error) {
 | 
			
		||||
	if m.ReturnErr {
 | 
			
		||||
		return 0, errors.New("mock error")
 | 
			
		||||
	}
 | 
			
		||||
	return m.ReturnCost, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---- Tests ----
 | 
			
		||||
 | 
			
		||||
func TestGetIDAndCreatorAndType(t *testing.T) {
 | 
			
		||||
	r := resources.PricedResource{
 | 
			
		||||
		ResourceID:   "res-123",
 | 
			
		||||
		CreatorID:    "user-abc",
 | 
			
		||||
		ResourceType: tools.DATA_RESOURCE,
 | 
			
		||||
	}
 | 
			
		||||
	assert.Equal(t, "res-123", r.GetID())
 | 
			
		||||
	assert.Equal(t, "user-abc", r.GetCreatorID())
 | 
			
		||||
	assert.Equal(t, tools.DATA_RESOURCE, r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsPurchased(t *testing.T) {
 | 
			
		||||
	t.Run("nil selected pricing returns false", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{}
 | 
			
		||||
		assert.False(t, r.IsPurchasable())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("returns true if pricing profile is purchased", func(t *testing.T) {
 | 
			
		||||
		mock := &MockPricingProfile{Purchased: true}
 | 
			
		||||
		r := &resources.PricedResource{SelectedPricing: mock}
 | 
			
		||||
		assert.True(t, r.IsPurchasable())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetAndSetLocationStartEnd(t *testing.T) {
 | 
			
		||||
	r := &resources.PricedResource{}
 | 
			
		||||
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	r.SetLocationStart(now)
 | 
			
		||||
	r.SetLocationEnd(now.Add(2 * time.Hour))
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, now, *r.GetLocationStart())
 | 
			
		||||
	assert.Equal(t, now.Add(2*time.Hour), *r.GetLocationEnd())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetExplicitDurationInS(t *testing.T) {
 | 
			
		||||
	t.Run("uses explicit duration if set", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{ExplicitBookingDurationS: 3600}
 | 
			
		||||
		assert.Equal(t, 3600.0, r.GetExplicitDurationInS())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("computes duration from start and end", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(2 * time.Hour)
 | 
			
		||||
		r := &resources.PricedResource{UsageStart: &start, UsageEnd: &end}
 | 
			
		||||
		assert.InDelta(t, 7200.0, r.GetExplicitDurationInS(), 0.1)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("defaults to 1 hour when times not set", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{}
 | 
			
		||||
		assert.InDelta(t, 3600.0, r.GetExplicitDurationInS(), 0.1)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetPrice(t *testing.T) {
 | 
			
		||||
	t.Run("returns error if no pricing profile", func(t *testing.T) {
 | 
			
		||||
		r := &resources.PricedResource{ResourceID: "no-profile"}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		assert.Contains(t, err.Error(), "pricing profile must be set")
 | 
			
		||||
		assert.Equal(t, 0.0, price)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("uses first profile if selected is nil", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(30 * time.Minute)
 | 
			
		||||
		r := &resources.PricedResource{
 | 
			
		||||
			UsageStart: &start,
 | 
			
		||||
			UsageEnd:   &end,
 | 
			
		||||
		}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, 42.0, price)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("returns error if profile GetPrice fails", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(1 * time.Hour)
 | 
			
		||||
		mock := &MockPricingProfile{ReturnErr: true}
 | 
			
		||||
		r := &resources.PricedResource{
 | 
			
		||||
			SelectedPricing: mock,
 | 
			
		||||
			UsageStart:      &start,
 | 
			
		||||
			UsageEnd:        &end,
 | 
			
		||||
		}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		assert.Equal(t, 0.0, price)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("uses SelectedPricing if set", func(t *testing.T) {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		end := start.Add(1 * time.Hour)
 | 
			
		||||
		mock := &MockPricingProfile{ReturnCost: 10.0}
 | 
			
		||||
		r := &resources.PricedResource{
 | 
			
		||||
			SelectedPricing: mock,
 | 
			
		||||
			UsageStart:      &start,
 | 
			
		||||
			UsageEnd:        &end,
 | 
			
		||||
		}
 | 
			
		||||
		price, err := r.GetPrice()
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, 10.0, price)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								models/resources/tests/processing_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								models/resources/tests/processing_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	. "cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestProcessingResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &ProcessingResource{}
 | 
			
		||||
	assert.Equal(t, tools.PROCESSING_RESOURCE.String(), r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedProcessingResource_GetType(t *testing.T) {
 | 
			
		||||
	r := &PricedProcessingResource{}
 | 
			
		||||
	assert.Equal(t, tools.PROCESSING_RESOURCE, r.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedProcessingResource_GetExplicitDurationInS(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	after := now.Add(2 * time.Hour)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		input    PricedProcessingResource
 | 
			
		||||
		expected float64
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Service without explicit duration",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				IsService: true,
 | 
			
		||||
			},
 | 
			
		||||
			expected: -1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Nil start time, non-service",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				PricedResource: PricedResource{
 | 
			
		||||
					UsageStart: nil,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: float64((1 * time.Hour).Seconds()),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Duration computed from start and end",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				PricedResource: PricedResource{
 | 
			
		||||
					UsageStart: &now,
 | 
			
		||||
					UsageEnd:   &after,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: float64((2 * time.Hour).Seconds()),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Explicit duration takes precedence",
 | 
			
		||||
			input: PricedProcessingResource{
 | 
			
		||||
				PricedResource: PricedResource{
 | 
			
		||||
					ExplicitBookingDurationS: 1337,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: 1337,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			assert.Equal(t, test.expected, test.input.GetExplicitDurationInS())
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestProcessingResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	request := &tools.APIRequest{}
 | 
			
		||||
	r := &ProcessingResource{}
 | 
			
		||||
	acc := r.GetAccessor(request)
 | 
			
		||||
	assert.NotNil(t, acc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestProcessingResourcePricingProfile_GetPrice(t *testing.T) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	end := start.Add(2 * time.Hour)
 | 
			
		||||
	mockPricing := pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
		Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{
 | 
			
		||||
			Price: 100.0,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	profile := &ProcessingResourcePricingProfile{mockPricing}
 | 
			
		||||
	price, err := profile.GetPrice(0, 0, start, end)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 100.0, price)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestProcessingResourcePricingProfile_IsPurchased(t *testing.T) {
 | 
			
		||||
	purchased := &ProcessingResourcePricingProfile{
 | 
			
		||||
		AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
 | 
			
		||||
			Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{
 | 
			
		||||
				BuyingStrategy: pricing.PERMANENT,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	assert.True(t, purchased.IsPurchasable())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								models/resources/tests/resource_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								models/resources/tests/resource_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockInstance struct {
 | 
			
		||||
	ID   string
 | 
			
		||||
	Name string
 | 
			
		||||
	resources.ResourceInstance[*MockPartner]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockInstance) GetID() string    { return m.ID }
 | 
			
		||||
func (m *MockInstance) GetName() string  { return m.Name }
 | 
			
		||||
func (m *MockInstance) ClearEnv()        {}
 | 
			
		||||
func (m *MockInstance) ClearPeerGroups() {}
 | 
			
		||||
func (m *MockInstance) GetProfile() pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MockInstance) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MockInstance) GetPeerGroups() ([]resources.ResourcePartnerITF, []map[string][]string) {
 | 
			
		||||
	return nil, []map[string][]string{
 | 
			
		||||
		{"peer1": {"group1"}},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MockPartner struct {
 | 
			
		||||
	groups map[string][]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPartner) GetProfile(buying int, strategy int) pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockPartner) GetPeerGroups() map[string][]string {
 | 
			
		||||
	return m.groups
 | 
			
		||||
}
 | 
			
		||||
func (m *MockPartner) ClearPeerGroups() {}
 | 
			
		||||
func (m *MockPartner) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MockDBObject struct {
 | 
			
		||||
	utils.AbstractObject
 | 
			
		||||
	isDraft bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockDBObject) IsDrafted() bool {
 | 
			
		||||
	return m.isDraft
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSelectedInstance_WithValidIndex(t *testing.T) {
 | 
			
		||||
	index := 1
 | 
			
		||||
	inst1 := &MockInstance{ID: "1"}
 | 
			
		||||
	inst2 := &MockInstance{ID: "2"}
 | 
			
		||||
	resource := &resources.AbstractInstanciatedResource[*MockInstance]{
 | 
			
		||||
		AbstractResource: resources.AbstractResource{SelectedInstanceIndex: &index},
 | 
			
		||||
		Instances:        []*MockInstance{inst1, inst2},
 | 
			
		||||
	}
 | 
			
		||||
	result := resource.GetSelectedInstance()
 | 
			
		||||
	assert.Equal(t, inst2, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSelectedInstance_NoIndex(t *testing.T) {
 | 
			
		||||
	inst := &MockInstance{ID: "1"}
 | 
			
		||||
	resource := &resources.AbstractInstanciatedResource[*MockInstance]{
 | 
			
		||||
		Instances: []*MockInstance{inst},
 | 
			
		||||
	}
 | 
			
		||||
	result := resource.GetSelectedInstance()
 | 
			
		||||
	assert.Equal(t, inst, result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate_WhenOnlyStateDiffers(t *testing.T) {
 | 
			
		||||
	resource := &resources.AbstractResource{AbstractObject: utils.AbstractObject{IsDraft: false}}
 | 
			
		||||
	set := &MockDBObject{isDraft: true}
 | 
			
		||||
	canUpdate, updated := resource.CanUpdate(set)
 | 
			
		||||
	assert.True(t, canUpdate)
 | 
			
		||||
	assert.Equal(t, set, updated)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAuthAction_WithMatchingGroup(t *testing.T) {
 | 
			
		||||
	inst := &MockInstance{
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*MockPartner]{
 | 
			
		||||
			Partnerships: []*MockPartner{
 | 
			
		||||
				{groups: map[string][]string{"peer1": {"group1"}}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	req := &tools.APIRequest{PeerID: "peer1", Groups: []string{"group1"}}
 | 
			
		||||
	result := resources.VerifyAuthAction([]*MockInstance{inst}, req)
 | 
			
		||||
	assert.Len(t, result, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FakeResource struct {
 | 
			
		||||
	resources.AbstractInstanciatedResource[*MockInstance]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FakeResource) Trim()                                 {}
 | 
			
		||||
func (f *FakeResource) SetAllowedInstances(*tools.APIRequest) {}
 | 
			
		||||
func (f *FakeResource) VerifyAuth(*tools.APIRequest) bool     { return true }
 | 
			
		||||
 | 
			
		||||
func TestNewAccessor_ReturnsValid(t *testing.T) {
 | 
			
		||||
	acc := resources.NewAccessor[*FakeResource](tools.COMPUTE_RESOURCE, &tools.APIRequest{}, func() utils.DBObject {
 | 
			
		||||
		return &FakeResource{}
 | 
			
		||||
	})
 | 
			
		||||
	assert.NotNil(t, acc)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										105
									
								
								models/resources/tests/storage_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								models/resources/tests/storage_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_GetType(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	assert.Equal(t, tools.STORAGE_RESOURCE.String(), res.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	req := &tools.APIRequest{}
 | 
			
		||||
	accessor := res.GetAccessor(req)
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_ConvertToPricedResource_ValidType(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	res.AbstractInstanciatedResource.CreatorID = "creator"
 | 
			
		||||
	res.AbstractInstanciatedResource.UUID = "res-id"
 | 
			
		||||
	priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.NotNil(t, priced)
 | 
			
		||||
	assert.IsType(t, &resources.PricedStorageResource{}, priced)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) {
 | 
			
		||||
	res := &resources.StorageResource{}
 | 
			
		||||
	priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{})
 | 
			
		||||
	assert.Nil(t, priced)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourceInstance_ClearEnv(t *testing.T) {
 | 
			
		||||
	inst := &resources.StorageResourceInstance{
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*resources.StorageResourcePartnership]{
 | 
			
		||||
			Env:     []models.Param{{Attr: "A"}},
 | 
			
		||||
			Inputs:  []models.Param{{Attr: "B"}},
 | 
			
		||||
			Outputs: []models.Param{{Attr: "C"}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inst.ClearEnv()
 | 
			
		||||
	assert.Empty(t, inst.Env)
 | 
			
		||||
	assert.Empty(t, inst.Inputs)
 | 
			
		||||
	assert.Empty(t, inst.Outputs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourceInstance_StoreDraftDefault(t *testing.T) {
 | 
			
		||||
	inst := &resources.StorageResourceInstance{
 | 
			
		||||
		Source: "my-source",
 | 
			
		||||
		ResourceInstance: resources.ResourceInstance[*resources.StorageResourcePartnership]{
 | 
			
		||||
			Env: []models.Param{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inst.StoreDraftDefault()
 | 
			
		||||
	assert.Len(t, inst.Env, 1)
 | 
			
		||||
	assert.Equal(t, "source", inst.Env[0].Attr)
 | 
			
		||||
	assert.Equal(t, "my-source", inst.Env[0].Value)
 | 
			
		||||
	assert.True(t, inst.Env[0].Readonly)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourcePricingStrategy_GetQuantity(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		strategy resources.StorageResourcePricingStrategy
 | 
			
		||||
		dataGB   float64
 | 
			
		||||
		expect   float64
 | 
			
		||||
	}{
 | 
			
		||||
		{resources.PER_DATA_STORED, 1.2, 1.2},
 | 
			
		||||
		{resources.PER_TB_STORED, 1.2, 1200},
 | 
			
		||||
		{resources.PER_GB_STORED, 2.5, 2.5},
 | 
			
		||||
		{resources.PER_MB_STORED, 1.0, 1000},
 | 
			
		||||
		{resources.PER_KB_STORED, 0.1, 100000},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		q, err := tt.strategy.GetQuantity(tt.dataGB)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, tt.expect, q)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageResourcePricingStrategy_GetQuantity_Invalid(t *testing.T) {
 | 
			
		||||
	invalid := resources.StorageResourcePricingStrategy(99)
 | 
			
		||||
	q, err := invalid.GetQuantity(1.0)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, 0.0, q)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPricedStorageResource_GetPrice_NoProfiles(t *testing.T) {
 | 
			
		||||
	res := &resources.PricedStorageResource{
 | 
			
		||||
		PricedResource: resources.PricedResource{
 | 
			
		||||
			ResourceID: "res-id",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	_, err := res.GetPrice()
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								models/resources/tests/workflow_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								models/resources/tests/workflow_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
package resources_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_GetType(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	assert.Equal(t, tools.WORKFLOW_RESOURCE.String(), w.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_ConvertToPricedResource(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{
 | 
			
		||||
		AbstractResource: resources.AbstractResource{
 | 
			
		||||
			AbstractObject: utils.AbstractObject{
 | 
			
		||||
				Name:      "Test Workflow",
 | 
			
		||||
				UUID:      "workflow-uuid",
 | 
			
		||||
				CreatorID: "creator-id",
 | 
			
		||||
			},
 | 
			
		||||
			Logo: "logo.png",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := &tools.APIRequest{
 | 
			
		||||
		PeerID: "peer-1",
 | 
			
		||||
		Groups: []string{"group1"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req)
 | 
			
		||||
	assert.Equal(t, "creator-id", pr.GetCreatorID())
 | 
			
		||||
	assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_ClearEnv(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	assert.Equal(t, w, w.ClearEnv())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_Trim(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	w.Trim()
 | 
			
		||||
	// nothing to assert; just test that it doesn't panic
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_SetAllowedInstances(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	w.SetAllowedInstances(&tools.APIRequest{})
 | 
			
		||||
	// no-op; just confirm no crash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWorkflowResource_GetAccessor(t *testing.T) {
 | 
			
		||||
	w := &resources.WorkflowResource{}
 | 
			
		||||
	request := &tools.APIRequest{}
 | 
			
		||||
	accessor := w.GetAccessor(request)
 | 
			
		||||
	assert.NotNil(t, accessor)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								models/resources/workflow.go
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								models/resources/workflow.go
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
package resources
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WorkflowResourcePricingProfile struct{}
 | 
			
		||||
 | 
			
		||||
// WorkflowResource is a struct that represents a workflow resource
 | 
			
		||||
// it defines the resource workflow
 | 
			
		||||
type WorkflowResource struct {
 | 
			
		||||
	AbstractResource
 | 
			
		||||
	WorkflowID string `bson:"workflow_id,omitempty" json:"workflow_id,omitempty"` // WorkflowID is the ID of the native workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WorkflowResource) GetType() string {
 | 
			
		||||
	return tools.WORKFLOW_RESOURCE.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) ClearEnv() utils.DBObject {
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) Trim() {
 | 
			
		||||
	/* EMPTY */
 | 
			
		||||
}
 | 
			
		||||
func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
 | 
			
		||||
	/* EMPTY */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
 | 
			
		||||
	return &PricedResource{
 | 
			
		||||
		Name:         w.Name,
 | 
			
		||||
		Logo:         w.Logo,
 | 
			
		||||
		ResourceID:   w.UUID,
 | 
			
		||||
		ResourceType: t,
 | 
			
		||||
		CreatorID:    w.CreatorID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
package oclib
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// WorkflowResource is a struct that represents a workflow resource
 | 
			
		||||
// it defines the resource workflow
 | 
			
		||||
type WorkflowResource struct {
 | 
			
		||||
	resource_model.AbstractResource
 | 
			
		||||
	WorkflowID string `bson:"workflow_id,omitempty" json:"workflow_id,omitempty"` // WorkflowID is the ID of the native workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowResource) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                              // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.WORKFLOW_RESOURCE, caller) // Initialize the accessor with the WORKFLOW_RESOURCE model type
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *WorkflowResource) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *WorkflowResource) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
package oclib
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type workflowResourceMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New() *workflowResourceMongoAccessor {
 | 
			
		||||
	return &workflowResourceMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *workflowResourceMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericDeleteOne(id, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *workflowResourceMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	set.(*WorkflowResource).ResourceModel = nil
 | 
			
		||||
	return wfa.GenericUpdateOne(set, id, wfa, &WorkflowResource{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *workflowResourceMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data.(*WorkflowResource).ResourceModel = nil
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *workflowResourceMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	res, _, _ := wfa.LoadOne(data.GetID())
 | 
			
		||||
	data.(*WorkflowResource).WorkflowID = data.GetID()
 | 
			
		||||
	if res == nil {
 | 
			
		||||
		return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
	} else {
 | 
			
		||||
		data.(*WorkflowResource).UUID = res.GetID()
 | 
			
		||||
		return wfa.GenericUpdateOne(data, res.GetID(), wfa, &WorkflowResource{})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *workflowResourceMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var workflow WorkflowResource
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&workflow)
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	if err == nil && len(resources) > 0 {
 | 
			
		||||
		workflow.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
	}
 | 
			
		||||
	return &workflow, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa workflowResourceMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []WorkflowResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Search searches for workflow resources in the database, given some filters OR a search string
 | 
			
		||||
func (wfa *workflowResourceMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by like name, short_description, description, owner, url if no filters are provided
 | 
			
		||||
				"abstractresource.abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.short_description":   {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.description":         {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.owner":               {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
				"abstractresource.source_url":          {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []WorkflowResource
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&resource_model.ResourceModel{}).GetAccessor(nil)
 | 
			
		||||
	resources, _, err := accessor.Search(nil, wfa.GetType())
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if err == nil && len(resources) > 0 {
 | 
			
		||||
			r.ResourceModel = resources[0].(*resource_model.ResourceModel)
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, &r.AbstractResource)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
package oclib
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resource_model"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStoreOneWorkflow(t *testing.T) {
 | 
			
		||||
	w := WorkflowResource{AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
 | 
			
		||||
		Description:    "Lorem Ipsum",
 | 
			
		||||
		Logo:           "azerty.com",
 | 
			
		||||
		Owner:          "toto",
 | 
			
		||||
		OwnerLogo:      "totoLogo",
 | 
			
		||||
		SourceUrl:      "azerty.fr",
 | 
			
		||||
	},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wma := New()
 | 
			
		||||
	id, _, _ := wma.StoreOne(&w)
 | 
			
		||||
 | 
			
		||||
	assert.NotEmpty(t, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOneWorkflow(t *testing.T) {
 | 
			
		||||
	w := WorkflowResource{AbstractResource: resource_model.AbstractResource{
 | 
			
		||||
		AbstractObject: utils.AbstractObject{Name: "testWorkflow"},
 | 
			
		||||
		Description:    "Lorem Ipsum",
 | 
			
		||||
		Logo:           "azerty.com",
 | 
			
		||||
		Owner:          "toto",
 | 
			
		||||
		OwnerLogo:      "totoLogo",
 | 
			
		||||
		SourceUrl:      "azerty.fr",
 | 
			
		||||
	},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wma := New()
 | 
			
		||||
	new_w, _, _ := wma.StoreOne(&w)
 | 
			
		||||
	assert.Equal(t, w, new_w)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								models/tests/models_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								models/tests/models_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestModel_ReturnsValidInstances(t *testing.T) {
 | 
			
		||||
	for name, _ := range models.ModelsCatalog {
 | 
			
		||||
		t.Run(name, func(t *testing.T) {
 | 
			
		||||
			modelInt, _ := strconv.Atoi(name)
 | 
			
		||||
			obj := models.Model(modelInt)
 | 
			
		||||
			assert.NotNil(t, obj, "Model() returned nil for valid model name %s", name)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestModel_UnknownModelReturnsNil(t *testing.T) {
 | 
			
		||||
	invalidModelInt := -9999 // unlikely to be valid
 | 
			
		||||
	obj := models.Model(invalidModelInt)
 | 
			
		||||
	assert.Nil(t, obj)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetModelsNames_ReturnsAllKeys(t *testing.T) {
 | 
			
		||||
	names := models.GetModelsNames()
 | 
			
		||||
	assert.Len(t, names, len(models.ModelsCatalog))
 | 
			
		||||
 | 
			
		||||
	seen := make(map[string]bool)
 | 
			
		||||
	for _, name := range names {
 | 
			
		||||
		seen[name] = true
 | 
			
		||||
	}
 | 
			
		||||
	for key := range models.ModelsCatalog {
 | 
			
		||||
		assert.Contains(t, seen, key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										242
									
								
								models/utils/abstracts.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										242
									
								
								models/utils/abstracts.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -2,11 +2,9 @@ package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
@@ -16,6 +14,13 @@ import (
 | 
			
		||||
// single instance of the validator used in every model Struct to validate the fields
 | 
			
		||||
var validate = validator.New(validator.WithRequiredStructEnabled())
 | 
			
		||||
 | 
			
		||||
type AccessMode int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Private AccessMode = iota
 | 
			
		||||
	Public
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* AbstractObject is a struct that represents the basic fields of an object
 | 
			
		||||
* it defines the object id and name
 | 
			
		||||
@@ -24,40 +29,22 @@ var validate = validator.New(validator.WithRequiredStructEnabled())
 | 
			
		||||
type AbstractObject struct {
 | 
			
		||||
	UUID          string     `json:"id,omitempty" bson:"id,omitempty" validate:"required"`
 | 
			
		||||
	Name          string     `json:"name,omitempty" bson:"name,omitempty" validate:"required"`
 | 
			
		||||
	IsDraft       bool       `json:"is_draft" bson:"is_draft" default:"false"`
 | 
			
		||||
	CreatorID     string     `json:"creator_id,omitempty" bson:"creator_id,omitempty"`
 | 
			
		||||
	UserCreatorID string     `json:"user_creator_id,omitempty" bson:"user_creator_id,omitempty"`
 | 
			
		||||
	CreationDate  time.Time  `json:"creation_date,omitempty" bson:"creation_date,omitempty"`
 | 
			
		||||
	UpdateDate    time.Time  `json:"update_date,omitempty" bson:"update_date,omitempty"`
 | 
			
		||||
	UpdaterID     string     `json:"updater_id,omitempty" bson:"updater_id,omitempty"`
 | 
			
		||||
	UserUpdaterID string     `json:"user_updater_id,omitempty" bson:"user_updater_id,omitempty"`
 | 
			
		||||
	AccessMode    AccessMode `json:"access_mode" bson:"access_mode" default:"0"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetID returns the id of the object (abstract)
 | 
			
		||||
func (ao *AbstractObject) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetName returns the name of the object (abstract)
 | 
			
		||||
func (ao *AbstractObject) GetName() string {
 | 
			
		||||
	return ao.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAccessor returns the accessor of the object (abstract)
 | 
			
		||||
func (dma *AbstractObject) GetAccessor(caller *tools.HTTPCaller) Accessor {
 | 
			
		||||
func (ri *AbstractObject) GetAccessor(request *tools.APIRequest) Accessor {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractObject) Deserialize(j map[string]interface{}) DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractObject) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
func (r *AbstractObject) SetID(id string) {
 | 
			
		||||
	r.UUID = id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) GenerateID() {
 | 
			
		||||
@@ -66,89 +53,124 @@ func (r *AbstractObject) GenerateID() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AbstractAccessor struct {
 | 
			
		||||
	Logger zerolog.Logger    // Logger is the logger of the accessor, it's a specilized logger for the accessor
 | 
			
		||||
	Type   string            // Type is the data type of the accessor
 | 
			
		||||
	Caller *tools.HTTPCaller // Caller is the http caller of the accessor (optionnal) only need in a peer connection
 | 
			
		||||
func (r *AbstractObject) StoreDraftDefault() {
 | 
			
		||||
	r.IsDraft = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetType() string {
 | 
			
		||||
func (r *AbstractObject) CanUpdate(set DBObject) (bool, DBObject) {
 | 
			
		||||
	return true, set
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) CanDelete() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractObject) IsDrafted() bool {
 | 
			
		||||
	return r.IsDraft
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetID implements ShallowDBObject.
 | 
			
		||||
func (ao AbstractObject) GetID() string {
 | 
			
		||||
	return ao.UUID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetName implements ShallowDBObject.
 | 
			
		||||
func (ao AbstractObject) GetName() string {
 | 
			
		||||
	return ao.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) GetCreatorID() string {
 | 
			
		||||
	return ao.CreatorID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) UpToDate(user string, peer string, create bool) {
 | 
			
		||||
	ao.UpdateDate = time.Now()
 | 
			
		||||
	ao.UpdaterID = peer
 | 
			
		||||
	ao.UserUpdaterID = user
 | 
			
		||||
	if create {
 | 
			
		||||
		ao.CreationDate = time.Now()
 | 
			
		||||
		ao.CreatorID = peer
 | 
			
		||||
		ao.UserCreatorID = user
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	return ao.AccessMode == Public || (request != nil && ao.CreatorID == request.PeerID && request.PeerID != "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *AbstractObject) GetObjectFilters(search string) *dbs.Filters {
 | 
			
		||||
	if search == "*" {
 | 
			
		||||
		search = ""
 | 
			
		||||
	}
 | 
			
		||||
	return &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
 | 
			
		||||
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
		}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractObject) Deserialize(j map[string]interface{}, obj DBObject) DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, obj)
 | 
			
		||||
	return obj
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractObject) Serialize(obj DBObject) map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(obj)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AbstractAccessor struct {
 | 
			
		||||
	Logger                zerolog.Logger    // Logger is the logger of the accessor, it's a specilized logger for the accessor
 | 
			
		||||
	Type                  tools.DataType    // Type is the data type of the accessor
 | 
			
		||||
	Request               *tools.APIRequest // Caller is the http caller of the accessor (optionnal) only need in a peer connection
 | 
			
		||||
	ResourceModelAccessor Accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractAccessor) ShouldVerifyAuth() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AbstractAccessor) GetRequest() *tools.APIRequest {
 | 
			
		||||
	return r.Request
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetUser() string {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.Username
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetPeerID() string {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.PeerID
 | 
			
		||||
}
 | 
			
		||||
func (dma *AbstractAccessor) GetGroups() []string {
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return []string{}
 | 
			
		||||
	}
 | 
			
		||||
	return dma.Request.Groups
 | 
			
		||||
}
 | 
			
		||||
func (dma *AbstractAccessor) GetLogger() *zerolog.Logger {
 | 
			
		||||
	return &dma.Logger
 | 
			
		||||
}
 | 
			
		||||
func (dma *AbstractAccessor) GetType() tools.DataType {
 | 
			
		||||
	return dma.Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *AbstractAccessor) GetCaller() *tools.HTTPCaller {
 | 
			
		||||
	return dma.Caller
 | 
			
		||||
	if dma.Request == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
// Init initializes the accessor with the data type and the http caller
 | 
			
		||||
func (dma *AbstractAccessor) Init(t DataType, caller *tools.HTTPCaller) {
 | 
			
		||||
	dma.Logger = logs.CreateLogger(t.String()) // Create a logger with the data type
 | 
			
		||||
	dma.Caller = caller                        // Set the caller
 | 
			
		||||
	dma.Type = t.String()                      // Set the data type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
func (wfa *AbstractAccessor) GenericStoreOne(data DBObject, accessor Accessor) (DBObject, int, error) {
 | 
			
		||||
	data.GenerateID()
 | 
			
		||||
	f := dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{
 | 
			
		||||
			"abstractresource.abstractobject.name": {{
 | 
			
		||||
				Operator: dbs.LIKE.String(),
 | 
			
		||||
				Value:    data.GetName(),
 | 
			
		||||
			}},
 | 
			
		||||
			"abstractobject.name": {{
 | 
			
		||||
				Operator: dbs.LIKE.String(),
 | 
			
		||||
				Value:    data.GetName(),
 | 
			
		||||
			}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	if cursor, _, _ := accessor.Search(&f, ""); len(cursor) > 0 {
 | 
			
		||||
		return nil, 409, errors.New(accessor.GetType() + " with name " + data.GetName() + " already exists")
 | 
			
		||||
	}
 | 
			
		||||
	err := validate.Struct(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 422, err
 | 
			
		||||
	}
 | 
			
		||||
	id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return accessor.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
func (dma *AbstractAccessor) GenericDeleteOne(id string, accessor Accessor) (DBObject, int, error) {
 | 
			
		||||
	res, code, err := accessor.LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		dma.Logger.Error().Msg("Could not retrieve " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	_, code, err = mongo.MONGOService.DeleteOne(id, accessor.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		dma.Logger.Error().Msg("Could not delete " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
 | 
			
		||||
func (dma *AbstractAccessor) GenericUpdateOne(set DBObject, id string, accessor Accessor, new DBObject) (DBObject, int, error) {
 | 
			
		||||
	r, c, err := accessor.LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, c, err
 | 
			
		||||
	}
 | 
			
		||||
	change := set.Serialize() // get the changes
 | 
			
		||||
	loaded := r.Serialize()   // get the loaded object
 | 
			
		||||
 | 
			
		||||
	for k, v := range change { // apply the changes, with a flatten method
 | 
			
		||||
		loaded[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded), id, accessor.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		dma.Logger.Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return accessor.LoadOne(id)
 | 
			
		||||
	return dma.Request.Caller
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										168
									
								
								models/utils/common.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										168
									
								
								models/utils/common.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,8 +1,166 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
type Price struct {
 | 
			
		||||
	Price float64 `json:"price,omitempty" bson:"price,omitempty"`
 | 
			
		||||
	Currency string `json:"currency,omitempty" bson:"currency,omitempty"`
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	mgb "go.mongodb.org/mongo-driver/mongo"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Owner struct {
 | 
			
		||||
	Name string `json:"name,omitempty" bson:"name,omitempty"`
 | 
			
		||||
	Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func VerifyAccess(a Accessor, id string) error {
 | 
			
		||||
	data, _, err := a.LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return errors.New("you are not allowed to access :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
func GenericStoreOne(data DBObject, a Accessor) (DBObject, int, error) {
 | 
			
		||||
	data.GenerateID()
 | 
			
		||||
	data.StoreDraftDefault()
 | 
			
		||||
	data.UpToDate(a.GetUser(), a.GetPeerID(), true)
 | 
			
		||||
	f := dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{
 | 
			
		||||
			"abstractresource.abstractobject.name": {{
 | 
			
		||||
				Operator: dbs.LIKE.String(),
 | 
			
		||||
				Value:    data.GetName(),
 | 
			
		||||
			}},
 | 
			
		||||
			"abstractobject.name": {{
 | 
			
		||||
				Operator: dbs.LIKE.String(),
 | 
			
		||||
				Value:    data.GetName(),
 | 
			
		||||
			}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access : " + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	if cursor, _, _ := a.Search(&f, "", data.IsDrafted()); len(cursor) > 0 {
 | 
			
		||||
		return nil, 409, errors.New(a.GetType().String() + " with name " + data.GetName() + " already exists")
 | 
			
		||||
	}
 | 
			
		||||
	err := validate.Struct(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 422, err
 | 
			
		||||
	}
 | 
			
		||||
	id, code, err := mongo.MONGOService.StoreOne(data, data.GetID(), a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not store " + data.GetName() + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return a.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) {
 | 
			
		||||
	res, code, err := a.LoadOne(id)
 | 
			
		||||
	if !res.CanDelete() {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to delete :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	if a.ShouldVerifyAuth() && !res.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access " + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	_, code, err = mongo.MONGOService.DeleteOne(id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not delete " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
 | 
			
		||||
func GenericUpdateOne(set DBObject, id string, a Accessor, new DBObject) (DBObject, int, error) {
 | 
			
		||||
	r, c, err := a.LoadOne(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, c, err
 | 
			
		||||
	}
 | 
			
		||||
	ok, newSet := r.CanUpdate(set)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to delete :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	set = newSet
 | 
			
		||||
	r.UpToDate(a.GetUser(), a.GetPeerID(), false)
 | 
			
		||||
	if a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	change := set.Serialize(set) // get the changes
 | 
			
		||||
	loaded := r.Serialize(r)     // get the loaded object
 | 
			
		||||
 | 
			
		||||
	for k, v := range change { // apply the changes, with a flatten method
 | 
			
		||||
		loaded[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	id, code, err := mongo.MONGOService.UpdateOne(new.Deserialize(loaded, new), id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return a.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenericLoadOne[T DBObject](id string, f func(DBObject) (DBObject, int, error), a Accessor) (DBObject, int, error) {
 | 
			
		||||
	var data T
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&data)
 | 
			
		||||
	if a.ShouldVerifyAuth() && !data.VerifyAuth(a.GetRequest()) {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to access :" + a.GetType().String())
 | 
			
		||||
	}
 | 
			
		||||
	return f(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func genericLoadAll[T DBObject](res *mgb.Cursor, code int, err error, onlyDraft bool, f func(DBObject) ShallowDBObject, a Accessor) ([]ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []ShallowDBObject{}
 | 
			
		||||
	var results []T
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	if err = res.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		if (a.ShouldVerifyAuth() && !r.VerifyAuth(a.GetRequest())) || f(r) == nil || (onlyDraft && !r.IsDrafted()) || (!onlyDraft && r.IsDrafted()) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		objs = append(objs, f(r))
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenericLoadAll[T DBObject](f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) {
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType().String())
 | 
			
		||||
	return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenericSearch[T DBObject](filters *dbs.Filters, search string, defaultFilters *dbs.Filters,
 | 
			
		||||
	f func(DBObject) ShallowDBObject, onlyDraft bool, wfa Accessor) ([]ShallowDBObject, int, error) {
 | 
			
		||||
	if filters == nil && search != "" {
 | 
			
		||||
		filters = defaultFilters
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType().String())
 | 
			
		||||
	return genericLoadAll[T](res_mongo, code, err, onlyDraft, f, wfa)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenericLoadOne loads one object from the database (generic)
 | 
			
		||||
// json expected in entry is a flatted object no need to respect the inheritance hierarchy
 | 
			
		||||
func GenericRawUpdateOne(set DBObject, id string, a Accessor) (DBObject, int, error) {
 | 
			
		||||
	id, code, err := mongo.MONGOService.UpdateOne(set, id, a.GetType().String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		a.GetLogger().Error().Msg("Could not update " + id + " to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	return a.LoadOne(id)
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
type DataType int
 | 
			
		||||
 | 
			
		||||
// DataType - Enum for the different types of resources in db accessible from the outside
 | 
			
		||||
const (
 | 
			
		||||
	INVALID DataType = iota
 | 
			
		||||
	DATA_RESOURCE
 | 
			
		||||
	PROCESSING_RESOURCE
 | 
			
		||||
	STORAGE_RESOURCE
 | 
			
		||||
	DATACENTER_RESOURCE
 | 
			
		||||
	WORKFLOW_RESOURCE
 | 
			
		||||
	WORKFLOW
 | 
			
		||||
	WORKFLOW_EXECUTION
 | 
			
		||||
	WORKSPACE
 | 
			
		||||
	RESOURCE_MODEL
 | 
			
		||||
	PEER
 | 
			
		||||
	SHARED_WORKSPACE
 | 
			
		||||
	RULE
 | 
			
		||||
	BOOKING
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bind the standard API name to the data type
 | 
			
		||||
var DefaultAPI = [...]string{
 | 
			
		||||
	"",
 | 
			
		||||
	"oc-catalog",
 | 
			
		||||
	"oc-catalog",
 | 
			
		||||
	"oc-catalog",
 | 
			
		||||
	"oc-catalog",
 | 
			
		||||
	"oc-catalog",
 | 
			
		||||
	"oc-workflow",
 | 
			
		||||
	"",
 | 
			
		||||
	"oc-workspace",
 | 
			
		||||
	"",
 | 
			
		||||
	"oc-peers",
 | 
			
		||||
	"oc-shared",
 | 
			
		||||
	"oc-shared",
 | 
			
		||||
	"oc-datacenter",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Bind the standard data name to the data type
 | 
			
		||||
var Str = [...]string{
 | 
			
		||||
	"invalid",
 | 
			
		||||
	"data_resource",
 | 
			
		||||
	"processing_resource",
 | 
			
		||||
	"storage_resource",
 | 
			
		||||
	"datacenter_resource",
 | 
			
		||||
	"workflow_resource",
 | 
			
		||||
	"workflow",
 | 
			
		||||
	"workflow_execution",
 | 
			
		||||
	"workspace",
 | 
			
		||||
	"resource_model",
 | 
			
		||||
	"peer",
 | 
			
		||||
	"shared_workspace",
 | 
			
		||||
	"rule",
 | 
			
		||||
	"booking",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FromInt(i int) string {
 | 
			
		||||
	return Str[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d DataType) API() string { // API - Returns the API name of the data type
 | 
			
		||||
	return DefaultAPI[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d DataType) String() string { // String - Returns the string name of the data type
 | 
			
		||||
	return Str[d]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnumIndex - Creating common behavior - give the type a EnumIndex functio
 | 
			
		||||
func (d DataType) EnumIndex() int {
 | 
			
		||||
	return int(d)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								models/utils/interfaces.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										32
									
								
								models/utils/interfaces.go
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -3,6 +3,7 @@ package utils
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ShallowDBObject is an interface that defines the basic methods shallowed version of a DBObject
 | 
			
		||||
@@ -10,30 +11,43 @@ type ShallowDBObject interface {
 | 
			
		||||
	GenerateID()
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetName() string
 | 
			
		||||
	Deserialize(j map[string]interface{}) DBObject
 | 
			
		||||
	Serialize() map[string]interface{}
 | 
			
		||||
	Serialize(obj DBObject) map[string]interface{}
 | 
			
		||||
	Deserialize(j map[string]interface{}, obj DBObject) DBObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DBObject is an interface that defines the basic methods for a DBObject
 | 
			
		||||
type DBObject interface {
 | 
			
		||||
	GenerateID()
 | 
			
		||||
	SetID(id string)
 | 
			
		||||
	GetID() string
 | 
			
		||||
	GetName() string
 | 
			
		||||
	Deserialize(j map[string]interface{}) DBObject
 | 
			
		||||
	Serialize() map[string]interface{}
 | 
			
		||||
	GetAccessor(caller *tools.HTTPCaller) Accessor
 | 
			
		||||
	IsDrafted() bool
 | 
			
		||||
	CanDelete() bool
 | 
			
		||||
	StoreDraftDefault()
 | 
			
		||||
	GetCreatorID() string
 | 
			
		||||
	UpToDate(user string, peer string, create bool)
 | 
			
		||||
	CanUpdate(set DBObject) (bool, DBObject)
 | 
			
		||||
	VerifyAuth(request *tools.APIRequest) bool
 | 
			
		||||
	Serialize(obj DBObject) map[string]interface{}
 | 
			
		||||
	GetAccessor(request *tools.APIRequest) Accessor
 | 
			
		||||
	Deserialize(j map[string]interface{}, obj DBObject) DBObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Accessor is an interface that defines the basic methods for an Accessor
 | 
			
		||||
type Accessor interface {
 | 
			
		||||
	Init(t DataType, caller *tools.HTTPCaller)
 | 
			
		||||
	GetType() string
 | 
			
		||||
	GetUser() string
 | 
			
		||||
	GetPeerID() string
 | 
			
		||||
	GetGroups() []string
 | 
			
		||||
	ShouldVerifyAuth() bool
 | 
			
		||||
	GetType() tools.DataType
 | 
			
		||||
	GetLogger() *zerolog.Logger
 | 
			
		||||
	GetCaller() *tools.HTTPCaller
 | 
			
		||||
	Search(filters *dbs.Filters, search string) ([]ShallowDBObject, int, error)
 | 
			
		||||
	LoadAll() ([]ShallowDBObject, int, error)
 | 
			
		||||
	GetRequest() *tools.APIRequest
 | 
			
		||||
	LoadOne(id string) (DBObject, int, error)
 | 
			
		||||
	DeleteOne(id string) (DBObject, int, error)
 | 
			
		||||
	CopyOne(data DBObject) (DBObject, int, error)
 | 
			
		||||
	StoreOne(data DBObject) (DBObject, int, error)
 | 
			
		||||
	LoadAll(isDraft bool) ([]ShallowDBObject, int, error)
 | 
			
		||||
	UpdateOne(set DBObject, id string) (DBObject, int, error)
 | 
			
		||||
	Search(filters *dbs.Filters, search string, isDraft bool) ([]ShallowDBObject, int, error)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								models/utils/tests/abstracts_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								models/utils/tests/abstracts_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
package models_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGenerateID(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	ao.GenerateID()
 | 
			
		||||
	assert.NotEmpty(t, ao.UUID)
 | 
			
		||||
	_, err := uuid.Parse(ao.UUID)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStoreDraftDefault(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{IsDraft: true}
 | 
			
		||||
	ao.StoreDraftDefault()
 | 
			
		||||
	assert.False(t, ao.IsDraft)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanUpdate(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	res, set := ao.CanUpdate(nil)
 | 
			
		||||
	assert.True(t, res)
 | 
			
		||||
	assert.Nil(t, set)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCanDelete(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	assert.True(t, ao.CanDelete())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsDrafted(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{IsDraft: true}
 | 
			
		||||
	assert.True(t, ao.IsDrafted())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetID(t *testing.T) {
 | 
			
		||||
	u := uuid.New().String()
 | 
			
		||||
	ao := &utils.AbstractObject{UUID: u}
 | 
			
		||||
	assert.Equal(t, u, ao.GetID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetName(t *testing.T) {
 | 
			
		||||
	name := "MyObject"
 | 
			
		||||
	ao := &utils.AbstractObject{Name: name}
 | 
			
		||||
	assert.Equal(t, name, ao.GetName())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetCreatorID(t *testing.T) {
 | 
			
		||||
	id := "creator-123"
 | 
			
		||||
	ao := &utils.AbstractObject{CreatorID: id}
 | 
			
		||||
	assert.Equal(t, id, ao.GetCreatorID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpToDate_CreateFalse(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	time.Sleep(time.Millisecond) // ensure time difference
 | 
			
		||||
	ao.UpToDate("user123", "peer456", false)
 | 
			
		||||
	assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
 | 
			
		||||
	assert.Equal(t, "peer456", ao.UpdaterID)
 | 
			
		||||
	assert.Equal(t, "user123", ao.UserUpdaterID)
 | 
			
		||||
	assert.True(t, ao.CreationDate.IsZero())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpToDate_CreateTrue(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	time.Sleep(time.Millisecond)
 | 
			
		||||
	ao.UpToDate("user123", "peer456", true)
 | 
			
		||||
	assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
 | 
			
		||||
	assert.WithinDuration(t, now, ao.CreationDate, time.Second)
 | 
			
		||||
	assert.Equal(t, "peer456", ao.UpdaterID)
 | 
			
		||||
	assert.Equal(t, "peer456", ao.CreatorID)
 | 
			
		||||
	assert.Equal(t, "user123", ao.UserUpdaterID)
 | 
			
		||||
	assert.Equal(t, "user123", ao.UserCreatorID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAuth(t *testing.T) {
 | 
			
		||||
	request := &tools.APIRequest{PeerID: "peer123"}
 | 
			
		||||
	ao := &utils.AbstractObject{CreatorID: "peer123"}
 | 
			
		||||
	assert.True(t, ao.VerifyAuth(request))
 | 
			
		||||
 | 
			
		||||
	ao = &utils.AbstractObject{AccessMode: utils.Public}
 | 
			
		||||
	assert.True(t, ao.VerifyAuth(nil))
 | 
			
		||||
 | 
			
		||||
	ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
 | 
			
		||||
	request = &tools.APIRequest{PeerID: "wrong"}
 | 
			
		||||
	assert.False(t, ao.VerifyAuth(request))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetObjectFilters(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	f := ao.GetObjectFilters("*")
 | 
			
		||||
	assert.NotNil(t, f)
 | 
			
		||||
	assert.Contains(t, f.Or, "abstractobject.name")
 | 
			
		||||
	assert.Equal(t, dbs.LIKE.String(), f.Or["abstractobject.name"][0].Operator)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeserialize(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{}
 | 
			
		||||
	input := map[string]interface{}{"name": "test", "id": uuid.New().String()}
 | 
			
		||||
	res := ao.Deserialize(input, &utils.AbstractObject{})
 | 
			
		||||
	assert.NotNil(t, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSerialize(t *testing.T) {
 | 
			
		||||
	ao := &utils.AbstractObject{Name: "test", UUID: uuid.New().String()}
 | 
			
		||||
	m := ao.Serialize(ao)
 | 
			
		||||
	assert.Equal(t, "test", m["name"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAbstractAccessorMethods(t *testing.T) {
 | 
			
		||||
	r := &utils.AbstractAccessor{Request: &tools.APIRequest{Username: "alice", PeerID: "peer1", Groups: []string{"dev"}}}
 | 
			
		||||
	assert.True(t, r.ShouldVerifyAuth())
 | 
			
		||||
	assert.Equal(t, "alice", r.GetUser())
 | 
			
		||||
	assert.Equal(t, "peer1", r.GetPeerID())
 | 
			
		||||
	assert.Equal(t, []string{"dev"}, r.GetGroups())
 | 
			
		||||
	assert.Equal(t, r.Request.Caller, r.GetCaller())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										168
									
								
								models/utils/tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								models/utils/tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
			
		||||
package models_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// --- Mock Definitions ---
 | 
			
		||||
 | 
			
		||||
type MockDBObject struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) GetLogger() *zerolog.Logger {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetGroups() []string {
 | 
			
		||||
	return []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) GetCaller() *tools.HTTPCaller {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockDBObject) GenerateID()        { m.Called() }
 | 
			
		||||
func (m *MockDBObject) StoreDraftDefault() { m.Called() }
 | 
			
		||||
func (m *MockDBObject) UpToDate(user, peer string, create bool) {
 | 
			
		||||
	m.Called(user, peer, create)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) VerifyAuth(req *tools.APIRequest) bool {
 | 
			
		||||
	args := m.Called(req)
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) CanDelete() bool {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
 | 
			
		||||
	args := m.Called(set)
 | 
			
		||||
	return args.Bool(0), args.Get(1).(utils.DBObject)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) IsDrafted() bool {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) Serialize(obj utils.DBObject) map[string]interface{} {
 | 
			
		||||
	args := m.Called(obj)
 | 
			
		||||
	return args.Get(0).(map[string]interface{})
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) Deserialize(mdata map[string]interface{}, obj utils.DBObject) utils.DBObject {
 | 
			
		||||
	args := m.Called(mdata, obj)
 | 
			
		||||
	return args.Get(0).(utils.DBObject)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) GetID() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockDBObject) GetName() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MockAccessor struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(isDraft)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(set, id)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	args := m.Called(data)
 | 
			
		||||
	return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) ShouldVerifyAuth() bool {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Bool(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetRequest() *tools.APIRequest {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Get(0).(*tools.APIRequest)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetType() tools.DataType {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Get(0).(tools.DataType)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) Search(filters *dbs.Filters, s string, d bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	args := m.Called(filters, s, d)
 | 
			
		||||
	return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccessor) GetUser() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
func (m *MockAccessor) GetPeerID() string {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.String(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Test Cases ---
 | 
			
		||||
 | 
			
		||||
func TestVerifyAccess_Authorized(t *testing.T) {
 | 
			
		||||
	mockObj := new(MockDBObject)
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
 | 
			
		||||
	req := &tools.APIRequest{PeerID: "peer"}
 | 
			
		||||
	mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
 | 
			
		||||
	mockAcc.On("ShouldVerifyAuth").Return(true)
 | 
			
		||||
	mockObj.On("VerifyAuth", req).Return(true)
 | 
			
		||||
	mockAcc.On("GetRequest").Return(req)
 | 
			
		||||
 | 
			
		||||
	err := utils.VerifyAccess(mockAcc, "123")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAccess_Unauthorized(t *testing.T) {
 | 
			
		||||
	mockObj := new(MockDBObject)
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
 | 
			
		||||
	req := &tools.APIRequest{PeerID: "peer"}
 | 
			
		||||
	mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
 | 
			
		||||
	mockAcc.On("ShouldVerifyAuth").Return(true)
 | 
			
		||||
	mockObj.On("VerifyAuth", req).Return(false)
 | 
			
		||||
	mockAcc.On("GetRequest").Return(req)
 | 
			
		||||
 | 
			
		||||
	err := utils.VerifyAccess(mockAcc, "123")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Contains(t, err.Error(), "not allowed")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyAccess_LoadError(t *testing.T) {
 | 
			
		||||
	mockAcc := new(MockAccessor)
 | 
			
		||||
 | 
			
		||||
	mockAcc.On("LoadOne", "bad-id").Return(nil, 404, errors.New("not found"))
 | 
			
		||||
 | 
			
		||||
	err := utils.VerifyAccess(mockAcc, "bad-id")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Equal(t, "not found", err.Error())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										147
									
								
								models/workflow/graph/graph.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								models/workflow/graph/graph.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
package graph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Graph is a struct that represents a graph
 | 
			
		||||
type Graph struct {
 | 
			
		||||
	Partial bool                 `json:"partial" default:"false"`                             // Partial is a flag that indicates if the graph is partial
 | 
			
		||||
	Zoom    float64              `bson:"zoom" json:"zoom" default:"1"`                        // Zoom is the graphical zoom of the graph
 | 
			
		||||
	Items   map[string]GraphItem `bson:"items" json:"items" default:"{}" validate:"required"` // Items is the list of elements in the graph
 | 
			
		||||
	Links   []GraphLink          `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *Graph) Clear(id string) {
 | 
			
		||||
	realItems := map[string]GraphItem{}
 | 
			
		||||
	for k, it := range g.Items {
 | 
			
		||||
		if k == id {
 | 
			
		||||
			realinks := []GraphLink{}
 | 
			
		||||
			for _, link := range g.Links {
 | 
			
		||||
				if link.Source.ID != id && link.Destination.ID != id {
 | 
			
		||||
					realinks = append(realinks, link)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			g.Links = realinks
 | 
			
		||||
			g.Partial = true
 | 
			
		||||
		} else {
 | 
			
		||||
			realItems[k] = it
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	g.Items = realItems
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsProcessing(item GraphItem) bool {
 | 
			
		||||
	return item.Processing != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsCompute(item GraphItem) bool {
 | 
			
		||||
	return item.Compute != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsData(item GraphItem) bool {
 | 
			
		||||
	return item.Data != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsStorage(item GraphItem) bool {
 | 
			
		||||
	return item.Storage != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wf *Graph) IsWorkflow(item GraphItem) bool {
 | 
			
		||||
	return item.Workflow != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface,
 | 
			
		||||
	f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (float64, float64) {
 | 
			
		||||
	nearestStart := float64(10000000000)
 | 
			
		||||
	oneIsInfinite := false
 | 
			
		||||
	longestDuration := float64(0)
 | 
			
		||||
	for _, link := range g.Links {
 | 
			
		||||
		for _, processing := range processings {
 | 
			
		||||
			var source string                                                                                                                             // source is the source of the link
 | 
			
		||||
			if link.Destination.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the destination is the processing and the source is not a compute
 | 
			
		||||
				source = link.Source.ID
 | 
			
		||||
			} else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute
 | 
			
		||||
				source = link.Destination.ID
 | 
			
		||||
			}
 | 
			
		||||
			priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request)
 | 
			
		||||
			if source != "" {
 | 
			
		||||
				if priced.GetLocationStart() != nil {
 | 
			
		||||
					near := float64(priced.GetLocationStart().Sub(start).Seconds())
 | 
			
		||||
					if near < nearestStart {
 | 
			
		||||
						nearestStart = near
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
				if priced.GetLocationEnd() != nil {
 | 
			
		||||
					duration := float64(priced.GetLocationEnd().Sub(*priced.GetLocationStart()).Seconds())
 | 
			
		||||
					if longestDuration < duration {
 | 
			
		||||
						longestDuration = duration
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					oneIsInfinite = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if oneIsInfinite {
 | 
			
		||||
		return nearestStart, -1
 | 
			
		||||
	}
 | 
			
		||||
	return nearestStart, longestDuration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
 | 
			
		||||
 */
 | 
			
		||||
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 {
 | 
			
		||||
	currents := []float64{}        // list of current time
 | 
			
		||||
	for _, link := range g.Links { // for each link
 | 
			
		||||
		var source string                                                                     // source is the source of the link
 | 
			
		||||
		if link.Destination.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the destination is the processing and the source is not a compute
 | 
			
		||||
			source = link.Source.ID
 | 
			
		||||
		} else if link.Source.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the source is the processing and the destination is not a compute
 | 
			
		||||
			source = link.Destination.ID
 | 
			
		||||
		}
 | 
			
		||||
		if source == "" { // if source is empty, continue
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		dt, r := g.GetResource(source) // get the resource of the source
 | 
			
		||||
		if r == nil {                  // if item is nil, continue
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		priced := r.ConvertToPricedResource(dt, request)
 | 
			
		||||
		current := priced.GetExplicitDurationInS() // get the explicit duration of the item
 | 
			
		||||
		if current < 0 {                           // if current is negative, its means that duration of a before could be infinite continue
 | 
			
		||||
			return current
 | 
			
		||||
		}
 | 
			
		||||
		current += g.GetAverageTimeProcessingBeforeStart(current, source, request) // get the average time before start of the source
 | 
			
		||||
		currents = append(currents, current)                                       // append the current to the currents
 | 
			
		||||
	}
 | 
			
		||||
	var max float64 // get the max time to wait dependancies to finish
 | 
			
		||||
	for _, current := range currents {
 | 
			
		||||
		if current > max {
 | 
			
		||||
			max = current
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return max
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
 | 
			
		||||
	if item, ok := g.Items[id]; ok {
 | 
			
		||||
		if item.Data != nil {
 | 
			
		||||
			return tools.DATA_RESOURCE, item.Data
 | 
			
		||||
		} else if item.Compute != nil {
 | 
			
		||||
			return tools.COMPUTE_RESOURCE, item.Compute
 | 
			
		||||
		} else if item.Workflow != nil {
 | 
			
		||||
			return tools.WORKFLOW_RESOURCE, item.Workflow
 | 
			
		||||
		} else if item.Processing != nil {
 | 
			
		||||
			return tools.PROCESSING_RESOURCE, item.Processing
 | 
			
		||||
		} else if item.Storage != nil {
 | 
			
		||||
			return tools.STORAGE_RESOURCE, item.Storage
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return tools.INVALID, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								models/workflow/graph/item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								models/workflow/graph/item.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package graph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GraphItem is a struct that represents an item in a graph
 | 
			
		||||
type GraphItem struct {
 | 
			
		||||
	ID                      string   `bson:"id" json:"id" validate:"required"`             // ID is the unique identifier of the item
 | 
			
		||||
	Width                   float64  `bson:"width" json:"width" validate:"required"`       // Width is the graphical width of the item
 | 
			
		||||
	Height                  float64  `bson:"height" json:"height" validate:"required"`     // Height is the graphical height of the item
 | 
			
		||||
	Position                Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item
 | 
			
		||||
	*resources.ItemResource          // ItemResource is the resource of the item affected to the item
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GraphItem) GetResource() (tools.DataType, resources.ResourceInterface) {
 | 
			
		||||
	if g.Data != nil {
 | 
			
		||||
		return tools.DATA_RESOURCE, g.Data
 | 
			
		||||
	} else if g.Compute != nil {
 | 
			
		||||
		return tools.COMPUTE_RESOURCE, g.Compute
 | 
			
		||||
	} else if g.Workflow != nil {
 | 
			
		||||
		return tools.WORKFLOW_RESOURCE, g.Workflow
 | 
			
		||||
	} else if g.Processing != nil {
 | 
			
		||||
		return tools.PROCESSING_RESOURCE, g.Processing
 | 
			
		||||
	} else if g.Storage != nil {
 | 
			
		||||
		return tools.STORAGE_RESOURCE, g.Storage
 | 
			
		||||
	}
 | 
			
		||||
	return tools.INVALID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GraphItem) Clear() {
 | 
			
		||||
	g.Data = nil
 | 
			
		||||
	g.Compute = nil
 | 
			
		||||
	g.Workflow = nil
 | 
			
		||||
	g.Processing = nil
 | 
			
		||||
	g.Storage = nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +1,12 @@
 | 
			
		||||
package graph
 | 
			
		||||
 | 
			
		||||
import "cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
import "cloud.o-forge.io/core/oc-lib/models/common/models"
 | 
			
		||||
 | 
			
		||||
// Graph is a struct that represents a graph
 | 
			
		||||
type Graph struct {
 | 
			
		||||
	Zoom  float64              `bson:"zoom" json:"zoom" default:"1"`                        // Zoom is the graphical zoom of the graph
 | 
			
		||||
	Items map[string]GraphItem `bson:"items" json:"items" default:"{}" validate:"required"` // Items is the list of elements in the graph
 | 
			
		||||
	Links []GraphLink          `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GraphItem is a struct that represents an item in a graph
 | 
			
		||||
type GraphItem struct {
 | 
			
		||||
	ID                      string   `bson:"id" json:"id" validate:"required"`             // ID is the unique identifier of the item
 | 
			
		||||
	Width                   float64  `bson:"width" json:"width" validate:"required"`       // Width is the graphical width of the item
 | 
			
		||||
	Height                  float64  `bson:"height" json:"height" validate:"required"`     // Height is the graphical height of the item
 | 
			
		||||
	Position                Position `bson:"position" json:"position" validate:"required"` // Position is the graphical position of the item
 | 
			
		||||
	*resources.ItemResource          // ItemResource is the resource of the item affected to the item
 | 
			
		||||
type StorageProcessingGraphLink struct {
 | 
			
		||||
	Write       bool   `json:"write" bson:"write"`
 | 
			
		||||
	Source      string `json:"source" bson:"source"`
 | 
			
		||||
	Destination string `json:"destination" bson:"destination"`
 | 
			
		||||
	FileName    string `json:"filename" bson:"filename"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GraphLink is a struct that represents a link between two items in a graph
 | 
			
		||||
@@ -23,6 +14,22 @@ type GraphLink struct {
 | 
			
		||||
	Source           Position                     `bson:"source" json:"source" validate:"required"`                         // Source is the source graphical position of the link
 | 
			
		||||
	Destination      Position                     `bson:"destination" json:"destination" validate:"required"`               // Destination is the destination graphical position of the link
 | 
			
		||||
	Style            *GraphLinkStyle              `bson:"style,omitempty" json:"style,omitempty"`                           // Style is the graphical style of the link
 | 
			
		||||
	StorageLinkInfos []StorageProcessingGraphLink `bson:"storage_link_infos,omitempty" json:"storage_link_infos,omitempty"` // StorageLinkInfo is the storage link info
 | 
			
		||||
	Env              []models.Param               `json:"env" bson:"env"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tool function to check if a link is a link between a compute and a resource
 | 
			
		||||
func (l *GraphLink) IsComputeLink(g Graph) (bool, string) {
 | 
			
		||||
	if g.Items == nil {
 | 
			
		||||
		return false, ""
 | 
			
		||||
	}
 | 
			
		||||
	if d, ok := g.Items[l.Source.ID]; ok && d.Compute != nil {
 | 
			
		||||
		return true, d.Compute.UUID
 | 
			
		||||
	}
 | 
			
		||||
	if d, ok := g.Items[l.Destination.ID]; ok && d.Compute != nil {
 | 
			
		||||
		return true, d.Compute.UUID
 | 
			
		||||
	}
 | 
			
		||||
	return false, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GraphLinkStyle is a struct that represents the style of a link in a graph
 | 
			
		||||
@@ -43,7 +50,7 @@ type GraphLinkStyle struct {
 | 
			
		||||
 | 
			
		||||
// Position is a struct that represents a graphical position
 | 
			
		||||
type Position struct {
 | 
			
		||||
	ID string  `json:"id" bson:"id"`                   // ID reprents ItemID (optionnal), TODO: rename to ItemID
 | 
			
		||||
	ID string  `json:"id" bson:"id"`                   // ID reprents ItemID (optionnal)
 | 
			
		||||
	X  float64 `json:"x" bson:"x" validate:"required"` // X is the graphical x position
 | 
			
		||||
	Y  float64 `json:"y" bson:"y" validate:"required"` // Y is the graphical y position
 | 
			
		||||
}
 | 
			
		||||
@@ -1,52 +1,140 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/workflow/graph"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow/graph"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* AbstractWorkflow is a struct that represents a workflow for resource or native workflow
 | 
			
		||||
* Warning: there is 2 types of workflows, the resource workflow and the native workflow
 | 
			
		||||
* native workflow is the one that you create to schedule an execution
 | 
			
		||||
* resource workflow is the one that is created to set our native workflow in catalog
 | 
			
		||||
 */
 | 
			
		||||
type AbstractWorkflow struct {
 | 
			
		||||
	resources.ResourceSet
 | 
			
		||||
	Graph          *graph.Graph      `bson:"graph,omitempty" json:"graph,omitempty"`       // Graph UI & logic representation of the workflow
 | 
			
		||||
	ScheduleActive bool              `json:"schedule_active" bson:"schedule_active"`       // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
 | 
			
		||||
	Schedule       *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
 | 
			
		||||
	Shared         []string          `json:"shared,omitempty" bson:"shared,omitempty"`     // Shared is the ID of the shared workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tool function to check if a link is a link between a datacenter and a resource
 | 
			
		||||
func (w *AbstractWorkflow) isDCLink(link graph.GraphLink) (bool, string) {
 | 
			
		||||
	if w.Graph == nil || w.Graph.Items == nil {
 | 
			
		||||
		return false, ""
 | 
			
		||||
	}
 | 
			
		||||
	if d, ok := w.Graph.Items[link.Source.ID]; ok && d.Datacenter != nil {
 | 
			
		||||
		return true, d.Datacenter.UUID
 | 
			
		||||
	}
 | 
			
		||||
	if d, ok := w.Graph.Items[link.Destination.ID]; ok && d.Datacenter != nil {
 | 
			
		||||
		return true, d.Datacenter.UUID
 | 
			
		||||
	}
 | 
			
		||||
	return false, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Workflow is a struct that represents a workflow
 | 
			
		||||
* it defines the native workflow
 | 
			
		||||
 */
 | 
			
		||||
type Workflow struct {
 | 
			
		||||
	utils.AbstractObject // AbstractObject contains the basic fields of an object (id, name)
 | 
			
		||||
	AbstractWorkflow     // AbstractWorkflow contains the basic fields of a workflow
 | 
			
		||||
	resources.ResourceSet
 | 
			
		||||
	Graph          *graph.Graph `bson:"graph,omitempty" json:"graph,omitempty"` // Graph UI & logic representation of the workflow
 | 
			
		||||
	ScheduleActive bool         `json:"schedule_active" bson:"schedule_active"` // ScheduleActive is a flag that indicates if the schedule is active, if not the workflow is not scheduled and no execution or booking will be set
 | 
			
		||||
	// Schedule       *WorkflowSchedule `bson:"schedule,omitempty" json:"schedule,omitempty"` // Schedule is the schedule of the workflow
 | 
			
		||||
	Shared []string `json:"shared,omitempty" bson:"shared,omitempty"` // Shared is the ID of the shared workflow     // AbstractWorkflow contains the basic fields of a workflow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Workflow) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessor(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Deps struct {
 | 
			
		||||
	Source string
 | 
			
		||||
	Dest   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) IsDependancy(id string) []Deps {
 | 
			
		||||
	dependancyOfIDs := []Deps{}
 | 
			
		||||
	for _, link := range w.Graph.Links {
 | 
			
		||||
		if _, ok := w.Graph.Items[link.Destination.ID]; !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		source := w.Graph.Items[link.Destination.ID].Processing
 | 
			
		||||
		if id == link.Source.ID && source != nil {
 | 
			
		||||
			dependancyOfIDs = append(dependancyOfIDs, Deps{Source: source.GetName(), Dest: link.Destination.ID})
 | 
			
		||||
		}
 | 
			
		||||
		sourceWF := w.Graph.Items[link.Destination.ID].Workflow
 | 
			
		||||
		if id == link.Source.ID && sourceWF != nil {
 | 
			
		||||
			dependancyOfIDs = append(dependancyOfIDs, Deps{Source: sourceWF.GetName(), Dest: link.Destination.ID})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return dependancyOfIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetDependencies(id string) (dependencies []Deps) {
 | 
			
		||||
	for _, link := range w.Graph.Links {
 | 
			
		||||
		if _, ok := w.Graph.Items[link.Source.ID]; !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		source := w.Graph.Items[link.Source.ID].Processing
 | 
			
		||||
		if id == link.Destination.ID && source != nil {
 | 
			
		||||
			dependencies = append(dependencies, Deps{Source: source.GetName(), Dest: link.Source.ID})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetGraphItems(f func(item graph.GraphItem) bool) (list_datas []graph.GraphItem) {
 | 
			
		||||
	for _, item := range w.Graph.Items {
 | 
			
		||||
		if f(item) {
 | 
			
		||||
			list_datas = append(list_datas, item)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetPricedItem(
 | 
			
		||||
	f func(item graph.GraphItem) bool, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) map[string]pricing.PricedItemITF {
 | 
			
		||||
	list_datas := map[string]pricing.PricedItemITF{}
 | 
			
		||||
	for _, item := range w.Graph.Items {
 | 
			
		||||
		if f(item) {
 | 
			
		||||
			dt, res := item.GetResource()
 | 
			
		||||
			ord := res.ConvertToPricedResource(dt, request)
 | 
			
		||||
			list_datas[res.GetID()] = ord
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return list_datas
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Related struct {
 | 
			
		||||
	Node  resources.ResourceInterface
 | 
			
		||||
	Links []graph.GraphLink
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Workflow) GetByRelatedProcessing(processingID string, g func(item graph.GraphItem) bool) map[string]Related {
 | 
			
		||||
	related := map[string]Related{}
 | 
			
		||||
	for _, link := range w.Graph.Links {
 | 
			
		||||
		nodeID := link.Destination.ID
 | 
			
		||||
		var node resources.ResourceInterface
 | 
			
		||||
		if g(w.Graph.Items[link.Source.ID]) {
 | 
			
		||||
			item := w.Graph.Items[link.Source.ID]
 | 
			
		||||
			_, node = item.GetResource()
 | 
			
		||||
		}
 | 
			
		||||
		if node == nil && g(w.Graph.Items[link.Destination.ID]) { // if the source is not a storage, we consider that the destination is the storage
 | 
			
		||||
			nodeID = link.Source.ID
 | 
			
		||||
			item := w.Graph.Items[link.Destination.ID] // and the processing is the source
 | 
			
		||||
			_, node = item.GetResource()               // we are looking for the storage as destination
 | 
			
		||||
		}
 | 
			
		||||
		if processingID == nodeID && node != nil { // if the storage is linked to the processing
 | 
			
		||||
			if _, ok := related[processingID]; !ok {
 | 
			
		||||
				related[processingID] = Related{}
 | 
			
		||||
			}
 | 
			
		||||
			rel := related[node.GetID()]
 | 
			
		||||
			rel.Node = node
 | 
			
		||||
			rel.Links = append(rel.Links, link)
 | 
			
		||||
			related[processingID] = rel
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return related
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ao *Workflow) VerifyAuth(request *tools.APIRequest) bool {
 | 
			
		||||
	isAuthorized := false
 | 
			
		||||
	if len(ao.Shared) > 0 {
 | 
			
		||||
		for _, shared := range ao.Shared {
 | 
			
		||||
			shared, code, _ := shallow_collaborative_area.NewAccessor(request).LoadOne(shared)
 | 
			
		||||
			if code != 200 || shared == nil {
 | 
			
		||||
				isAuthorized = false
 | 
			
		||||
			} else {
 | 
			
		||||
				isAuthorized = shared.VerifyAuth(request)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ao.AbstractObject.VerifyAuth(request) || isAuthorized
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -57,19 +145,19 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
 | 
			
		||||
	if wfa.Graph == nil { // no graph no booking
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	accessor := (&datacenter.DatacenterResource{}).GetAccessor(nil)
 | 
			
		||||
	accessor := (&resources.ComputeResource{}).GetAccessor(&tools.APIRequest{Caller: caller})
 | 
			
		||||
	for _, link := range wfa.Graph.Links {
 | 
			
		||||
		if ok, dc_id := wfa.isDCLink(link); ok { // check if the link is a link between a datacenter and a resource
 | 
			
		||||
			dc, code, _ := accessor.LoadOne(dc_id)
 | 
			
		||||
		if ok, compute_id := link.IsComputeLink(*wfa.Graph); ok { // check if the link is a link between a compute and a resource
 | 
			
		||||
			compute, code, _ := accessor.LoadOne(compute_id)
 | 
			
		||||
			if code != 200 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// CHECK BOOKING ON PEER, datacenter could be a remote one
 | 
			
		||||
			peerID := dc.(*datacenter.DatacenterResource).PeerID
 | 
			
		||||
			// CHECK BOOKING ON PEER, compute could be a remote one
 | 
			
		||||
			peerID := compute.(*resources.ComputeResource).CreatorID
 | 
			
		||||
			if peerID == "" {
 | 
			
		||||
				return false, errors.New("no peer id")
 | 
			
		||||
			} // no peer id no booking, we need to know where to book
 | 
			
		||||
			_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, dc_id, utils.BOOKING, tools.GET, nil, caller)
 | 
			
		||||
			_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, compute_id, tools.BOOKING, tools.GET, nil, caller)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -78,31 +166,96 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Workflow) GetName() string {
 | 
			
		||||
	return d.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Workflow) GetAccessor(caller *tools.HTTPCaller) utils.Accessor {
 | 
			
		||||
	data := New()                     // Create a new instance of the accessor
 | 
			
		||||
	data.Init(utils.WORKFLOW, caller) // Initialize the accessor with the WORKFLOW model type
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Workflow) Deserialize(j map[string]interface{}) utils.DBObject {
 | 
			
		||||
	b, err := json.Marshal(j)
 | 
			
		||||
func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
 | 
			
		||||
	priceds := map[tools.DataType]map[string]pricing.PricedItemITF{}
 | 
			
		||||
	ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing,
 | 
			
		||||
		func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
			return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS()
 | 
			
		||||
		}, func(started time.Time, duration float64) *time.Time {
 | 
			
		||||
			s := started.Add(time.Duration(duration))
 | 
			
		||||
			return &s
 | 
			
		||||
		})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
		return 0, priceds, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	json.Unmarshal(b, dma)
 | 
			
		||||
	return dma
 | 
			
		||||
	if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request,
 | 
			
		||||
		wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
			return start, 0
 | 
			
		||||
		}, func(started time.Time, duration float64) *time.Time {
 | 
			
		||||
			return end
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
		return 0, priceds, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} {
 | 
			
		||||
		if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request,
 | 
			
		||||
			f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
				nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
 | 
			
		||||
					if f(i) {
 | 
			
		||||
						_, r = i.GetResource()
 | 
			
		||||
					}
 | 
			
		||||
					return r
 | 
			
		||||
				}, request)
 | 
			
		||||
				return start.Add(time.Duration(nearestStart) * time.Second), longestDuration
 | 
			
		||||
			}, func(started time.Time, duration float64) *time.Time {
 | 
			
		||||
				s := started.Add(time.Duration(duration))
 | 
			
		||||
				return &s
 | 
			
		||||
			}); err != nil {
 | 
			
		||||
			return 0, priceds, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	longest := common.GetPlannerLongestTime(end, priceds, request)
 | 
			
		||||
	if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow,
 | 
			
		||||
		func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
 | 
			
		||||
			start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second)
 | 
			
		||||
			longest := float64(-1)
 | 
			
		||||
			r, code, err := res.GetAccessor(request).LoadOne(res.GetID())
 | 
			
		||||
			if code != 200 || err != nil {
 | 
			
		||||
				return start, longest
 | 
			
		||||
			}
 | 
			
		||||
			if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil {
 | 
			
		||||
				return start, longest
 | 
			
		||||
			} else if neoLongest > longest {
 | 
			
		||||
				longest = neoLongest
 | 
			
		||||
			}
 | 
			
		||||
			return start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second), longest
 | 
			
		||||
		}, func(start time.Time, longest float64) *time.Time {
 | 
			
		||||
			s := start.Add(time.Duration(longest) * time.Second)
 | 
			
		||||
			return &s
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
		return 0, priceds, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return longest, priceds, wf, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dma *Workflow) Serialize() map[string]interface{} {
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	b, err := json.Marshal(dma)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
func plan[T resources.ResourceInterface](
 | 
			
		||||
	dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest,
 | 
			
		||||
	f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) {
 | 
			
		||||
	resources := []T{}
 | 
			
		||||
	for _, item := range wf.GetGraphItems(f) {
 | 
			
		||||
		if priceds[dt] == nil {
 | 
			
		||||
			priceds[dt] = map[string]pricing.PricedItemITF{}
 | 
			
		||||
		}
 | 
			
		||||
	json.Unmarshal(b, &m)
 | 
			
		||||
	return m
 | 
			
		||||
		dt, realItem := item.GetResource()
 | 
			
		||||
		if realItem == nil {
 | 
			
		||||
			return resources, priceds, errors.New("could not load the processing resource")
 | 
			
		||||
		}
 | 
			
		||||
		priced := realItem.ConvertToPricedResource(dt, request)
 | 
			
		||||
		// Should be commented once the Pricing selection feature has been implemented, related to the commit d35ad440fa77763ec7f49ab34a85e47e75581b61
 | 
			
		||||
		// if priced.SelectPricing() == nil {
 | 
			
		||||
		// 	return resources, priceds, errors.New("no pricings are selected... can't proceed")
 | 
			
		||||
		// }
 | 
			
		||||
		started, duration := start(realItem, priced)
 | 
			
		||||
		priced.SetLocationStart(started)
 | 
			
		||||
		if duration >= 0 {
 | 
			
		||||
			if e := end(started, duration); e != nil {
 | 
			
		||||
				priced.SetLocationEnd(*e)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if e := end(started, priced.GetExplicitDurationInS()); e != nil {
 | 
			
		||||
			priced.SetLocationEnd(*e)
 | 
			
		||||
		}
 | 
			
		||||
		resources = append(resources, realItem.(T))
 | 
			
		||||
		priceds[dt][item.ID] = priced
 | 
			
		||||
	}
 | 
			
		||||
	return resources, priceds, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								models/workflow/workflow_history_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								models/workflow/workflow_history_mongo_accessor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WorkflowHistory struct{ Workflow }
 | 
			
		||||
 | 
			
		||||
func (d *WorkflowHistory) GetAccessor(request *tools.APIRequest) utils.Accessor {
 | 
			
		||||
	return NewAccessorHistory(request) // Create a new instance of the accessor
 | 
			
		||||
}
 | 
			
		||||
func (r *WorkflowHistory) GenerateID() {
 | 
			
		||||
	r.UUID = uuid.New().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Workspace is a struct that represents a workspace
 | 
			
		||||
type workflowHistoryMongoAccessor struct {
 | 
			
		||||
	workflowMongoAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the workspaceMongoAccessor
 | 
			
		||||
func NewHistory() *workflowHistoryMongoAccessor {
 | 
			
		||||
	return &workflowHistoryMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *workflowHistoryMongoAccessor) MapFromWorkflow(w *Workflow) *WorkflowHistory {
 | 
			
		||||
	wh := &WorkflowHistory{Workflow: *w}
 | 
			
		||||
	wh.GenerateID()
 | 
			
		||||
	return wh
 | 
			
		||||
}
 | 
			
		||||
@@ -2,372 +2,223 @@ package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/dbs/mongo"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/logs"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/collaborative_area/shallow_collaborative_area"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/peer"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/resources/datacenter"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/utils"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/models/workspace/shared/shallow_shared_workspace"
 | 
			
		||||
	"cloud.o-forge.io/core/oc-lib/tools"
 | 
			
		||||
	cron "github.com/robfig/cron/v3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type workflowMongoAccessor struct {
 | 
			
		||||
	utils.AbstractAccessor    // AbstractAccessor contains the basic fields of an accessor (model, caller)
 | 
			
		||||
	computeResourceAccessor   utils.Accessor
 | 
			
		||||
	collaborativeAreaAccessor utils.Accessor
 | 
			
		||||
	workspaceAccessor         utils.Accessor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessorHistory(request *tools.APIRequest) *workflowMongoAccessor {
 | 
			
		||||
	return new(tools.WORKFLOW_HISTORY, request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAccessor(request *tools.APIRequest) *workflowMongoAccessor {
 | 
			
		||||
	return new(tools.WORKFLOW, request)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new instance of the workflowMongoAccessor
 | 
			
		||||
func New() *workflowMongoAccessor {
 | 
			
		||||
	return &workflowMongoAccessor{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* THERE IS A LOT IN THIS FILE SHOULD BE AWARE OF THE COMMENTS
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* getExecutions is a function that returns the executions of a workflow
 | 
			
		||||
* it returns an array of workflow_execution.WorkflowExecution
 | 
			
		||||
 */
 | 
			
		||||
func (wfa *workflowMongoAccessor) getExecutions(id string, data *Workflow) ([]*workflow_execution.WorkflowExecution, error) {
 | 
			
		||||
	workflows_execution := []*workflow_execution.WorkflowExecution{}
 | 
			
		||||
	if data.Schedule != nil { // only set execution on a scheduled workflow
 | 
			
		||||
		if data.Schedule.Start == nil { // if no start date, return an error
 | 
			
		||||
			return workflows_execution, errors.New("should get a start date on the scheduler.")
 | 
			
		||||
		}
 | 
			
		||||
		if data.Schedule.End != nil && data.Schedule.End.IsZero() { // if end date is zero, set it to nil
 | 
			
		||||
			data.Schedule.End = nil
 | 
			
		||||
		}
 | 
			
		||||
		if len(data.Schedule.Cron) > 0 { // if cron is set then end date should be set
 | 
			
		||||
			if data.Schedule.End == nil {
 | 
			
		||||
				return workflows_execution, errors.New("a cron task should have an end date.")
 | 
			
		||||
			}
 | 
			
		||||
			cronStr := strings.Split(data.Schedule.Cron, " ") // split the cron string to treat it
 | 
			
		||||
			if len(cronStr) < 6 {                             // if the cron string is less than 6 fields, return an error because format is : ss mm hh dd MM dw (6 fields)
 | 
			
		||||
				return nil, errors.New("Bad cron message: " + data.Schedule.Cron + ". Should be at least ss mm hh dd MM dw")
 | 
			
		||||
			}
 | 
			
		||||
			subCron := strings.Join(cronStr[:6], " ")
 | 
			
		||||
			// cron should be parsed as ss mm hh dd MM dw t (min 6 fields)
 | 
			
		||||
			specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) // create a new cron parser
 | 
			
		||||
			sched, err := specParser.Parse(subCron)                                                                // parse the cron string
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return workflows_execution, errors.New("Bad cron message: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			// loop through the cron schedule to set the executions
 | 
			
		||||
			for s := sched.Next(*data.Schedule.Start); !s.IsZero() && s.Before(*data.Schedule.End); s = sched.Next(s) {
 | 
			
		||||
				obj := &workflow_execution.WorkflowExecution{
 | 
			
		||||
					AbstractObject: utils.AbstractObject{
 | 
			
		||||
						Name: data.Schedule.Name, // set the name of the execution
 | 
			
		||||
func new(t tools.DataType, request *tools.APIRequest) *workflowMongoAccessor {
 | 
			
		||||
	return &workflowMongoAccessor{
 | 
			
		||||
		computeResourceAccessor:   (&resources.ComputeResource{}).GetAccessor(request),
 | 
			
		||||
		collaborativeAreaAccessor: (&shallow_collaborative_area.ShallowCollaborativeArea{}).GetAccessor(request),
 | 
			
		||||
		workspaceAccessor:         (&workspace.Workspace{}).GetAccessor(request),
 | 
			
		||||
		AbstractAccessor: utils.AbstractAccessor{
 | 
			
		||||
			Logger:  logs.CreateLogger(t.String()), // Create a logger with the data type
 | 
			
		||||
			Request: request,
 | 
			
		||||
			Type:    t,
 | 
			
		||||
		},
 | 
			
		||||
					ExecDate:   &s,                // set the execution date
 | 
			
		||||
					EndDate:    data.Schedule.End, // set the end date
 | 
			
		||||
					State:      1,                 // set the state to 1 (scheduled)
 | 
			
		||||
					WorkflowID: id,                // set the workflow id dependancy of the execution
 | 
			
		||||
	}
 | 
			
		||||
				workflows_execution = append(workflows_execution, obj) // append the execution to the array
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		} else { // if no cron, set the execution to the start date
 | 
			
		||||
			obj := &workflow_execution.WorkflowExecution{ // create a new execution
 | 
			
		||||
				AbstractObject: utils.AbstractObject{
 | 
			
		||||
					Name: data.Schedule.Name,
 | 
			
		||||
				},
 | 
			
		||||
				ExecDate:   data.Schedule.Start,
 | 
			
		||||
				EndDate:    data.Schedule.End,
 | 
			
		||||
				State:      1,
 | 
			
		||||
				WorkflowID: id,
 | 
			
		||||
			}
 | 
			
		||||
			workflows_execution = append(workflows_execution, obj) // append the execution to the array
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return workflows_execution, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteOne deletes a workflow from the database, delete depending executions and bookings
 | 
			
		||||
func (wfa *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	wfa.execution(id, &Workflow{
 | 
			
		||||
		AbstractWorkflow: AbstractWorkflow{ScheduleActive: false},
 | 
			
		||||
	}, true) // delete the executions
 | 
			
		||||
	res, code, err := wfa.GenericDeleteOne(id, wfa)
 | 
			
		||||
func (a *workflowMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	res, code, err := utils.GenericDeleteOne(id, a)
 | 
			
		||||
	if res != nil && code == 200 {
 | 
			
		||||
		wfa.execute(res.(*Workflow), false) // up to date the workspace for the workflow
 | 
			
		||||
		a.execute(res.(*Workflow), true, false) // up to date the workspace for the workflow
 | 
			
		||||
		a.share(res.(*Workflow), true, a.GetCaller())
 | 
			
		||||
	}
 | 
			
		||||
	wfa.share(res.(*Workflow), true, wfa.Caller) // send the deletion to the peers where workflow is shared
 | 
			
		||||
	return res, code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* book is a function that books a workflow on the peers
 | 
			
		||||
* it takes the workflow id, the real data and the executions
 | 
			
		||||
* it returns an error if the booking fails
 | 
			
		||||
 */
 | 
			
		||||
func (wfa *workflowMongoAccessor) book(id string, realData *Workflow, execs []*workflow_execution.WorkflowExecution) error {
 | 
			
		||||
	if wfa.Caller == nil || wfa.Caller.URLS == nil || wfa.Caller.URLS[utils.BOOKING.String()] == nil {
 | 
			
		||||
		return errors.New("no caller defined")
 | 
			
		||||
	}
 | 
			
		||||
	methods := wfa.Caller.URLS[utils.BOOKING.String()]
 | 
			
		||||
	if _, ok := methods[tools.POST]; !ok {
 | 
			
		||||
		return errors.New("no path found")
 | 
			
		||||
	}
 | 
			
		||||
	res, code, _ := wfa.LoadOne(id)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return errors.New("could not load workflow")
 | 
			
		||||
	}
 | 
			
		||||
	r := res.(*Workflow)
 | 
			
		||||
	g := r.Graph
 | 
			
		||||
	if realData.Graph != nil { // if the graph is set, set it to the real data
 | 
			
		||||
		g = realData.Graph
 | 
			
		||||
	}
 | 
			
		||||
	if g != nil && g.Links != nil && len(g.Links) > 0 { // if the graph is set and has links then book the workflow (even on ourselves)
 | 
			
		||||
		accessor := (&datacenter.DatacenterResource{}).GetAccessor(nil)
 | 
			
		||||
		for _, link := range g.Links {
 | 
			
		||||
			if ok, dc_id := realData.isDCLink(link); ok { // check if the link is a link between a datacenter and a resource booking is only on datacenter
 | 
			
		||||
				dc, code, _ := accessor.LoadOne(dc_id)
 | 
			
		||||
				if code != 200 {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				// CHECK BOOKING
 | 
			
		||||
				peerID := dc.(*datacenter.DatacenterResource).PeerID
 | 
			
		||||
				if peerID == "" { // no peer id no booking
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				// BOOKING ON PEER
 | 
			
		||||
				_, err := (&peer.Peer{}).LaunchPeerExecution(peerID, "", utils.BOOKING, tools.POST,
 | 
			
		||||
					(&workflow_execution.WorkflowExecutions{ // it's the standard model for booking see OC-PEER
 | 
			
		||||
						WorkflowID: id,    // set the workflow id "WHO"
 | 
			
		||||
						ResourceID: dc_id, // set the datacenter id "WHERE"
 | 
			
		||||
						Executions: execs, // set the executions to book "WHAT"
 | 
			
		||||
					}).Serialize(), wfa.Caller)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return a.verifyResource(res), code, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* share is a function that shares a workflow to the peers if the workflow is shared
 | 
			
		||||
 */
 | 
			
		||||
func (wfa *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *tools.HTTPCaller) {
 | 
			
		||||
	if realData.Shared == nil || len(realData.Shared) == 0 { // no shared no sharing
 | 
			
		||||
func (a *workflowMongoAccessor) share(realData *Workflow, delete bool, caller *tools.HTTPCaller) {
 | 
			
		||||
	if realData == nil || realData.Shared == nil || len(realData.Shared) == 0 || caller == nil || caller.Disabled { // no shared no sharing
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, sharedID := range realData.Shared { // loop through the shared ids
 | 
			
		||||
		access := (&shallow_shared_workspace.ShallowSharedWorkspace{}).GetAccessor(nil)
 | 
			
		||||
		res, code, _ := access.LoadOne(sharedID)
 | 
			
		||||
		res, code, _ := a.collaborativeAreaAccessor.LoadOne(sharedID)
 | 
			
		||||
		if code != 200 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var err error
 | 
			
		||||
		paccess := &peer.Peer{}
 | 
			
		||||
		for _, p := range res.(*shallow_shared_workspace.ShallowSharedWorkspace).Peers {
 | 
			
		||||
		for _, p := range res.(*shallow_collaborative_area.ShallowCollaborativeArea).Peers {
 | 
			
		||||
			paccess.UUID = p
 | 
			
		||||
			if paccess.IsMySelf() { // if the peer is the current peer, never share because it will create a loop
 | 
			
		||||
			if ok, _ := paccess.IsMySelf(); ok { // if the peer is the current peer, never share because it will create a loop
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if delete { // if the workflow is deleted, share the deletion
 | 
			
		||||
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), utils.WORKFLOW, tools.DELETE, map[string]interface{}{}, caller)
 | 
			
		||||
			} else { // if the workflow is updated, share the update
 | 
			
		||||
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), utils.WORKFLOW, tools.PUT, res.Serialize(), caller)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			wfa.Logger.Error().Msg(err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
			if delete { // if the workflow is deleted, share the deletion	orderResourceAccessor     utils.Accessor
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* execution is a create or delete function for the workflow executions depending on the schedule of the workflow
 | 
			
		||||
 */
 | 
			
		||||
func (wfa *workflowMongoAccessor) execution(id string, realData *Workflow, delete bool) (int, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	nats := tools.NewNATSCaller() // create a new nats caller because executions are sent to the nats for daemons
 | 
			
		||||
	if !realData.ScheduleActive { // if the schedule is not active, delete the executions
 | 
			
		||||
		mongo.MONGOService.DeleteMultiple(map[string]interface{}{
 | 
			
		||||
			"state":       1, // only delete the scheduled executions only scheduled if executions are in progress or ended, they should not be deleted for registration
 | 
			
		||||
			"workflow_id": id,
 | 
			
		||||
		}, utils.WORKFLOW_EXECUTION.String())
 | 
			
		||||
		err := wfa.book(id, realData, []*workflow_execution.WorkflowExecution{}) // delete the booking of the workflow on the peers
 | 
			
		||||
		nats.SetNATSPub(utils.WORKFLOW.String(), tools.REMOVE, realData)         // send the deletion to the nats
 | 
			
		||||
		return 200, err
 | 
			
		||||
				history := NewHistory()
 | 
			
		||||
				history.StoreOne(history.MapFromWorkflow(res.(*Workflow)))
 | 
			
		||||
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.DELETE,
 | 
			
		||||
					map[string]interface{}{}, caller)
 | 
			
		||||
			} else { // if the workflow is updated, share the update
 | 
			
		||||
				_, err = paccess.LaunchPeerExecution(p, res.GetID(), tools.WORKFLOW, tools.PUT,
 | 
			
		||||
					res.Serialize(res), caller)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	accessor := (&workflow_execution.WorkflowExecution{}).GetAccessor(nil)
 | 
			
		||||
	execs, err := wfa.getExecutions(id, realData) // get the executions of the workflow
 | 
			
		||||
		if err != nil {
 | 
			
		||||
		return 422, err
 | 
			
		||||
	}
 | 
			
		||||
	err = wfa.book(id, realData, execs) // book the workflow on the peers
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 409, err // if the booking fails, return an error for integrity between peers
 | 
			
		||||
	}
 | 
			
		||||
	if delete { // if delete is set to true, delete the executions
 | 
			
		||||
		mongo.MONGOService.DeleteMultiple(map[string]interface{}{
 | 
			
		||||
			"workflow_id": id,
 | 
			
		||||
			"state":       1,
 | 
			
		||||
		}, utils.WORKFLOW_EXECUTION.String())
 | 
			
		||||
		wfa.book(id, realData, []*workflow_execution.WorkflowExecution{})
 | 
			
		||||
		nats.SetNATSPub(utils.WORKFLOW.String(), tools.REMOVE, realData)
 | 
			
		||||
		return 200, nil
 | 
			
		||||
	}
 | 
			
		||||
	if len(execs) > 0 { // if the executions are set, store them
 | 
			
		||||
		for _, obj := range execs {
 | 
			
		||||
			_, code, err := accessor.StoreOne(obj)
 | 
			
		||||
			if code != 200 {
 | 
			
		||||
				return code, err
 | 
			
		||||
			a.Logger.Error().Msg(err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
		nats.SetNATSPub(utils.WORKFLOW.String(), tools.CREATE, realData) // send the creation to the nats
 | 
			
		||||
	} else {
 | 
			
		||||
		return 422, err
 | 
			
		||||
	}
 | 
			
		||||
	return 200, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateOne updates a workflow in the database
 | 
			
		||||
func (wfa *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	res, code, err := wfa.LoadOne(id)
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return nil, 409, err
 | 
			
		||||
	}
 | 
			
		||||
func (a *workflowMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
 | 
			
		||||
	// avoid the update if the schedule is the same
 | 
			
		||||
	avoid := set.(*Workflow).Schedule == nil || (res.(*Workflow).Schedule != nil && res.(*Workflow).ScheduleActive == set.(*Workflow).ScheduleActive && res.(*Workflow).Schedule.Start == set.(*Workflow).Schedule.Start && res.(*Workflow).Schedule.End == set.(*Workflow).Schedule.End && res.(*Workflow).Schedule.Cron == set.(*Workflow).Schedule.Cron)
 | 
			
		||||
	res, code, err = wfa.GenericUpdateOne(set, id, wfa, &Workflow{})
 | 
			
		||||
	set = a.verifyResource(set)
 | 
			
		||||
	if set.(*Workflow).Graph != nil && set.(*Workflow).Graph.Partial {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to update a partial workflow")
 | 
			
		||||
	}
 | 
			
		||||
	res, code, err := utils.GenericUpdateOne(set, id, a, &Workflow{})
 | 
			
		||||
	if code != 200 {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	if !avoid { // if the schedule is not avoided, update the executions
 | 
			
		||||
		if code, err := wfa.execution(id, res.(*Workflow), true); err != nil {
 | 
			
		||||
			return nil, code, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	wfa.execute(res.(*Workflow), false)           // update the workspace for the workflow
 | 
			
		||||
	wfa.share(res.(*Workflow), false, wfa.Caller) // share the update to the peers
 | 
			
		||||
	return res, code, err
 | 
			
		||||
	workflow := res.(*Workflow)
 | 
			
		||||
	a.execute(workflow, false, true)        // update the workspace for the workflow
 | 
			
		||||
	a.share(workflow, false, a.GetCaller()) // share the update to the peers
 | 
			
		||||
	return res, code, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreOne stores a workflow in the database
 | 
			
		||||
func (wfa *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	res, code, err := wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
func (a *workflowMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	data = a.verifyResource(data)
 | 
			
		||||
	d := data.(*Workflow)
 | 
			
		||||
	if d.Graph != nil && d.Graph.Partial {
 | 
			
		||||
		return nil, 403, errors.New("you are not allowed to update a partial workflow")
 | 
			
		||||
	}
 | 
			
		||||
	res, code, err := utils.GenericStoreOne(d, a)
 | 
			
		||||
	if err != nil || code != 200 {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	wfa.share(res.(*Workflow), false, wfa.Caller) // share the creation to the peers
 | 
			
		||||
	//store the executions
 | 
			
		||||
	if code, err := wfa.execution(res.GetID(), res.(*Workflow), false); err != nil {
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	wfa.execute(res.(*Workflow), false) // store the workspace for the workflow
 | 
			
		||||
	return res, code, err
 | 
			
		||||
	workflow := res.(*Workflow)
 | 
			
		||||
 | 
			
		||||
	a.share(workflow, false, a.GetCaller()) // share the creation to the peers
 | 
			
		||||
	a.execute(workflow, false, true)        // store the workspace for the workflow
 | 
			
		||||
	return res, code, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CopyOne copies a workflow in the database
 | 
			
		||||
func (wfa *workflowMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	return wfa.GenericStoreOne(data, wfa)
 | 
			
		||||
func (a *workflowMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
	wf := data.(*Workflow)
 | 
			
		||||
	for _, item := range wf.Graph.Items {
 | 
			
		||||
		_, obj := item.GetResource()
 | 
			
		||||
		if obj != nil {
 | 
			
		||||
			obj.ClearEnv()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return utils.GenericStoreOne(data, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// execute is a function that executes a workflow
 | 
			
		||||
// it stores the workflow resources in a specific workspace to never have a conflict in UI and logic
 | 
			
		||||
func (wfa *workflowMongoAccessor) execute(workflow *Workflow, delete bool) {
 | 
			
		||||
 | 
			
		||||
	accessor := (&workspace.Workspace{}).GetAccessor(nil)
 | 
			
		||||
func (a *workflowMongoAccessor) execute(workflow *Workflow, delete bool, active bool) {
 | 
			
		||||
	filters := &dbs.Filters{
 | 
			
		||||
		Or: map[string][]dbs.Filter{ // filter by standard workspace name attached to a workflow
 | 
			
		||||
			"abstractobject.name": {{dbs.LIKE.String(), workflow.Name + "_workspace"}},
 | 
			
		||||
			"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: workflow.Name + "_workspace"}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	resource, _, err := accessor.Search(filters, "")
 | 
			
		||||
	resource, _, err := a.workspaceAccessor.Search(filters, "", workflow.IsDraft)
 | 
			
		||||
	if delete { // if delete is set to true, delete the workspace
 | 
			
		||||
		for _, r := range resource {
 | 
			
		||||
			accessor.DeleteOne(r.GetID())
 | 
			
		||||
			a.workspaceAccessor.DeleteOne(r.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil && len(resource) > 0 { // if the workspace already exists, update it
 | 
			
		||||
		accessor.UpdateOne(&workspace.Workspace{
 | 
			
		||||
			Active: true,
 | 
			
		||||
		a.workspaceAccessor.UpdateOne(&workspace.Workspace{
 | 
			
		||||
			Active: active,
 | 
			
		||||
			ResourceSet: resources.ResourceSet{
 | 
			
		||||
				Datas:       workflow.Datas,
 | 
			
		||||
				Processings: workflow.Processings,
 | 
			
		||||
				Storages:    workflow.Storages,
 | 
			
		||||
				Workflows:   workflow.Workflows,
 | 
			
		||||
				Datacenters: workflow.Datacenters,
 | 
			
		||||
				Computes:    workflow.Computes,
 | 
			
		||||
			},
 | 
			
		||||
		}, resource[0].GetID())
 | 
			
		||||
	} else { // if the workspace does not exist, create it
 | 
			
		||||
		accessor.StoreOne(&workspace.Workspace{
 | 
			
		||||
			Active:         true,
 | 
			
		||||
		a.workspaceAccessor.StoreOne(&workspace.Workspace{
 | 
			
		||||
			Active:         active,
 | 
			
		||||
			AbstractObject: utils.AbstractObject{Name: workflow.Name + "_workspace"},
 | 
			
		||||
			ResourceSet: resources.ResourceSet{
 | 
			
		||||
				Datas:       workflow.Datas,
 | 
			
		||||
				Processings: workflow.Processings,
 | 
			
		||||
				Storages:    workflow.Storages,
 | 
			
		||||
				Workflows:   workflow.Workflows,
 | 
			
		||||
				Datacenters: workflow.Datacenters,
 | 
			
		||||
				Computes:    workflow.Computes,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadOne loads a workflow from the database
 | 
			
		||||
func (wfa *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	var workflow Workflow
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadOne(id, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve " + id + " from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo.Decode(&workflow)
 | 
			
		||||
	wfa.execute(&workflow, false) // if no workspace is attached to the workflow, create it
 | 
			
		||||
 | 
			
		||||
	return &workflow, 200, nil
 | 
			
		||||
func (a *workflowMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadOne[*Workflow](id, func(d utils.DBObject) (utils.DBObject, int, error) {
 | 
			
		||||
		w := d.(*Workflow)
 | 
			
		||||
		a.execute(w, false, true) // if no workspace is attached to the workflow, create it
 | 
			
		||||
		return d, 200, nil
 | 
			
		||||
	}, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadAll loads all the workflows from the database
 | 
			
		||||
func (wfa workflowMongoAccessor) LoadAll() ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.LoadAll(wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not retrieve any from db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []Workflow
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r.AbstractObject) // only AbstractObject fields !
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
func (a *workflowMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericLoadAll[*Workflow](func(d utils.DBObject) utils.ShallowDBObject { return &d.(*Workflow).AbstractObject }, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wfa *workflowMongoAccessor) Search(filters *dbs.Filters, search string) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	objs := []utils.ShallowDBObject{}
 | 
			
		||||
	if (filters == nil || len(filters.And) == 0 || len(filters.Or) == 0) && search != "" {
 | 
			
		||||
		filters = &dbs.Filters{
 | 
			
		||||
			Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
 | 
			
		||||
				"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search}},
 | 
			
		||||
			},
 | 
			
		||||
func (a *workflowMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
 | 
			
		||||
	return utils.GenericSearch[*Workflow](filters, search, (&Workflow{}).GetObjectFilters(search), func(d utils.DBObject) utils.ShallowDBObject { return a.verifyResource(d) }, isDraft, a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *workflowMongoAccessor) verifyResource(obj utils.DBObject) utils.DBObject {
 | 
			
		||||
	wf := obj.(*Workflow)
 | 
			
		||||
	if wf.Graph == nil {
 | 
			
		||||
		return wf
 | 
			
		||||
	}
 | 
			
		||||
	for _, item := range wf.Graph.Items {
 | 
			
		||||
		t, resource := item.GetResource()
 | 
			
		||||
		if resource == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var access utils.Accessor
 | 
			
		||||
		if t == tools.COMPUTE_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.ComputeResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ComputeResource{} })
 | 
			
		||||
		} else if t == tools.PROCESSING_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.ProcessingResource](t, a.GetRequest(), func() utils.DBObject { return &resources.ProcessingResource{} })
 | 
			
		||||
		} else if t == tools.STORAGE_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.StorageResource](t, a.GetRequest(), func() utils.DBObject { return &resources.StorageResource{} })
 | 
			
		||||
		} else if t == tools.WORKFLOW_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.WorkflowResource](t, a.GetRequest(), func() utils.DBObject { return &resources.WorkflowResource{} })
 | 
			
		||||
		} else if t == tools.DATA_RESOURCE {
 | 
			
		||||
			access = resources.NewAccessor[*resources.DataResource](t, a.GetRequest(), func() utils.DBObject { return &resources.DataResource{} })
 | 
			
		||||
		} else {
 | 
			
		||||
			wf.Graph.Clear(resource.GetID())
 | 
			
		||||
		}
 | 
			
		||||
		if error := utils.VerifyAccess(access, resource.GetID()); error != nil {
 | 
			
		||||
			wf.Graph.Clear(resource.GetID())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	res_mongo, code, err := mongo.MONGOService.Search(filters, wfa.GetType())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		wfa.Logger.Error().Msg("Could not store to db. Error: " + err.Error())
 | 
			
		||||
		return nil, code, err
 | 
			
		||||
	}
 | 
			
		||||
	var results []Workflow
 | 
			
		||||
	if err = res_mongo.All(mongo.MngoCtx, &results); err != nil {
 | 
			
		||||
		return nil, 404, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		objs = append(objs, &r)
 | 
			
		||||
	}
 | 
			
		||||
	return objs, 200, nil
 | 
			
		||||
	return wf
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
package workflow
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
// WorkflowSchedule is a struct that contains the scheduling information of a workflow
 | 
			
		||||
type ScheduleMode int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TASK ScheduleMode = iota
 | 
			
		||||
	SERVICE
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* WorkflowSchedule is a struct that contains the scheduling information of a workflow
 | 
			
		||||
* It contains the mode of the schedule (Task or Service), the name of the schedule, the start and end time of the schedule and the cron expression
 | 
			
		||||
 */
 | 
			
		||||
type WorkflowSchedule struct {
 | 
			
		||||
	Mode  int64      `json:"mode" bson:"mode" validate:"required"`               // Mode is the mode of the schedule (Task or Service)
 | 
			
		||||
	Name  string     `json:"name" bson:"name" validate:"required"`               // Name is the name of the schedule
 | 
			
		||||
	Start *time.Time `json:"start" bson:"start" validate:"required,ltfield=End"` // Start is the start time of the schedule, is required and must be less than the End time
 | 
			
		||||
	End   *time.Time `json:"end,omitempty" bson:"end,omitempty"`                 // End is the end time of the schedule
 | 
			
		||||
	Cron  string     `json:"cron,omitempty" bson:"cron,omitempty"`               // here the cron format : ss mm hh dd MM dw task
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user