diff --git a/functionaltests/api/base.py b/functionaltests/api/base.py index fa6a21c6f..2f2d7f523 100644 --- a/functionaltests/api/base.py +++ b/functionaltests/api/base.py @@ -13,12 +13,15 @@ # under the License. import json +import os +import requests +import testtools +import uuid + from tempest import clients from tempest.common import rest_client from tempest import config from tempest import exceptions -import testtools -import uuid CONF = config.CONF @@ -142,16 +145,32 @@ class MuranoClient(rest_client.RestClient): return resp, json.loads(body) - def update_package(self, id): - post_body = [ - { - "op": "add", - "path": "/tags", - "value": ["i'm a test"] - } - ] + def upload_package(self, package_name, body): + __location__ = os.path.realpath(os.path.join( + os.getcwd(), os.path.dirname(__file__))) - resp, body = self.patch('catalog/packages/{0}'.format(id), post_body) + headers = {'X-Auth-Token': self.auth_provider.get_token()} + + files = {'%s' % package_name: open( + os.path.join(__location__, 'v1/DummyTestApp.zip'), 'rb')} + + post_body = {'JsonString': json.dumps(body)} + request_url = '{endpoint}{url}'.format(endpoint=self.base_url, + url='/catalog/packages') + + resp = requests.post(request_url, files=files, data=post_body, + headers=headers) + + return resp + + def update_package(self, id, post_body): + headers = { + 'X-Auth-Token': self.auth_provider.get_token(), + 'content-type': 'application/murano-packages-json-patch' + } + + resp, body = self.patch('catalog/packages/{0}'.format(id), + json.dumps(post_body), headers=headers) return resp, json.loads(body) diff --git a/functionaltests/api/v1/DummyTestApp/Classes/Controller.yaml b/functionaltests/api/v1/DummyTestApp/Classes/Controller.yaml new file mode 100644 index 000000000..b1eadcb5d --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Classes/Controller.yaml @@ -0,0 +1,19 @@ +Namespaces: + =: io.murano.windows.Dummy + std: io.murano + sys: io.murano.system + win: io.murano.windows + +Name: Controller + +Properties: + host: + Contract: $.class(win:Host).notNull() + + recoveryPassword: + Contract: $.string().notNull() + Default: P@ssw0rd + +Workflow: + deploy: + Body: $.host.deploy() diff --git a/functionaltests/api/v1/DummyTestApp/Classes/DomainHost.yaml b/functionaltests/api/v1/DummyTestApp/Classes/DomainHost.yaml new file mode 100644 index 000000000..68706f4a0 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Classes/DomainHost.yaml @@ -0,0 +1,20 @@ +Namespaces: + =: io.murano.windows + ad: io.murano.windows.Dummy + +Name: DomainHost + +Extends: Host + +Properties: + domain: + Contract: $.class(ad:Dummy).notNull() + +Workflow: + deploy: + Arguments: + Body: + - $.super($.deploy()) + #- $.joinDomain($.domain) + # Workaround against broken ResourceManager: + - $.super($.joinDomain($this.domain)) diff --git a/functionaltests/api/v1/DummyTestApp/Classes/Dummy.yaml b/functionaltests/api/v1/DummyTestApp/Classes/Dummy.yaml new file mode 100644 index 000000000..0f3d7859c --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Classes/Dummy.yaml @@ -0,0 +1,37 @@ +Namespaces: + =: io.murano.windows.Dummy + std: io.murano + sys: io.murano.system + +Name: Dummy + +Extends: std:Application + +Properties: + name: + Contract: $.string().notNull() + + primaryController: + Contract: $.class(PrimaryController).notNull() + + secondaryControllers: + Contract: [$.class(SecondaryController).notNull()] + + adminAccountName: + Contract: $.string().notNull() + Default: Administrator + + adminPassword: + Contract: $.string().notNull() + Default: P@ssw0rd + +Workflow: + deploy: + Body: + - $.primaryController.deploy() + - $.secondaryControllers.pselect($.deploy()) + - $.reportDeployed(title => 'Dummy', + unitCount => len(secondaryControllers) + 1) + + destroy: + - $.reportDestroyed() diff --git a/functionaltests/api/v1/DummyTestApp/Classes/Host.yaml b/functionaltests/api/v1/DummyTestApp/Classes/Host.yaml new file mode 100644 index 000000000..88be19531 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Classes/Host.yaml @@ -0,0 +1,49 @@ +Namespaces: + =: io.murano.windows + ad: io.murano.windows.Dummy + res: io.murano.resources + sys: io.murano.system + +Name: Host + +Extends: res:Instance + +Properties: + adminAccountName: + Contract: $.string().notNull() + Default: Administrator + + adminPassword: + Contract: $.string().notNull() + +Workflow: + initialize: + Body: + - $.super($.initialize()) + + deploy: + Body: + - $.super($.deploy()) + + - $resources: new(sys:Resources) + - $template: $resources.json('SetPassword.template').bind(dict( + adminPassword => $.adminPassword + )) + - $.agent.send($template, $resources) + + joinDomain: + Arguments: + - domain: + Contract: $.class(ad:Dummy).notNull() + Body: + + - $resources: new(sys:Resources) + - $template: $resources.json('JoinDomain.template').bind(dict( + domain => $domain.name, + domainUser => $domain.adminAccountName, + domainPassword => $domain.adminPassword, + ouPath => '', + dnsIp => $domain.primaryController.dnsIp + )) + - $.agent.call($template, $resources) + diff --git a/functionaltests/api/v1/DummyTestApp/Classes/PrimaryController.yaml b/functionaltests/api/v1/DummyTestApp/Classes/PrimaryController.yaml new file mode 100644 index 000000000..5bae8a50d --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Classes/PrimaryController.yaml @@ -0,0 +1,33 @@ +Namespaces: + =: io.murano.windows.Dummy + std: io.murano + sys: io.murano.system + +Name: PrimaryController + +Extends: Controller + +Properties: + + dnsIp: + Contract: $.string() + +Workflow: + initialize: + Body: + - $.super($.initialize()) + - $.domain: $.find(Dummy).require() + + deploy: + Arguments: + Body: + - $.super($.deploy()) + - $resources: new(io.murano.system.Resources) + - $template: $resources.json('CreatePrimaryDC.template').bind(dict( + domain => $.domain.name, + recoveryPassword => $.recoveryPassword + )) + - $.host.agent.call($template, $resources) + + - $template: $resources.json('AskDnsIp.template') + - $.dnsIp: $.host.agent.call($template, $resources)[0] diff --git a/functionaltests/api/v1/DummyTestApp/Classes/SecondaryController.yaml b/functionaltests/api/v1/DummyTestApp/Classes/SecondaryController.yaml new file mode 100644 index 000000000..ff9eee209 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Classes/SecondaryController.yaml @@ -0,0 +1,28 @@ +Namespaces: + =: io.murano.windows.Dummy + std: io.murano + sys: io.murano.system + +Name: SecondaryController + +Extends: Controller + +Workflow: + initialize: + Body: + - $.super($.initialize()) + - $.domain: $.find(ActiveDirectory).require() + + deploy: + Body: + - $.super($.deploy()) + - $.host.joinDomain($.domain) + - $resources: new(sys:Resources) + - $template: $resources.json('CreateSecondaryDC.template').bind(dict( + domain => $.domain.name, + recoveryPassword => $.recoveryPassword, + domainAccountName => $.domain.adminAccountName, + domainPassword => $.domain.adminPassword + )) + - $.host.agent.call($template, $resources) +# diff --git a/functionaltests/api/v1/DummyTestApp/Resources/AskDnsIp.template b/functionaltests/api/v1/DummyTestApp/Resources/AskDnsIp.template new file mode 100644 index 000000000..6d6bd4020 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/AskDnsIp.template @@ -0,0 +1,12 @@ +{ + "Scripts": [ + "Get-DnsListeningIpAddress.ps1" + ], + "Commands": [ + { + "Name": "Get-DnsListeningIpAddress", + "Arguments": {} + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/functionaltests/api/v1/DummyTestApp/Resources/CreatePrimaryDC.template b/functionaltests/api/v1/DummyTestApp/Resources/CreatePrimaryDC.template new file mode 100644 index 000000000..6633057d1 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/CreatePrimaryDC.template @@ -0,0 +1,16 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Install-RolePrimaryDomainController.ps1" + ], + "Commands": [ + { + "Name": "Install-RolePrimaryDomainController", + "Arguments": { + "DomainName": "$domain", + "SafeModePassword": "$recoveryPassword" + } + } + ], + "RebootOnCompletion": 1 +} diff --git a/functionaltests/api/v1/DummyTestApp/Resources/CreateSecondaryDC.template b/functionaltests/api/v1/DummyTestApp/Resources/CreateSecondaryDC.template new file mode 100644 index 000000000..4512ee5a6 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/CreateSecondaryDC.template @@ -0,0 +1,18 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Install-RoleSecondaryDomainController.ps1" + ], + "Commands": [ + { + "Name": "Install-RoleSecondaryDomainController", + "Arguments": { + "DomainName": "$domain", + "UserName": "$domainAccountName", + "Password": "$domainPassword", + "SafeModePassword": "$recoveryPassword" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/functionaltests/api/v1/DummyTestApp/Resources/JoinDomain.template b/functionaltests/api/v1/DummyTestApp/Resources/JoinDomain.template new file mode 100644 index 000000000..3d8cbeff8 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/JoinDomain.template @@ -0,0 +1,25 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Join-Domain.ps1" + ], + "Commands": [ + { + "Name": "Set-NetworkAdapterConfiguration", + "Arguments": { + "FirstAvailable": true, + "DNSServer": "$dnsIp" + } + }, + { + "Name": "Join-Domain", + "Arguments": { + "Username": "$domainUser", + "Password": "$domainPassword", + "DomainName": "$domain", + "OUPath": "$ouPath" + } + } + ], + "RebootOnCompletion": 1 +} \ No newline at end of file diff --git a/functionaltests/api/v1/DummyTestApp/Resources/SetPassword.template b/functionaltests/api/v1/DummyTestApp/Resources/SetPassword.template new file mode 100644 index 000000000..101db0ead --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/SetPassword.template @@ -0,0 +1,17 @@ +{ + "Scripts": [ + "ImportCoreFunctions.ps1", + "Set-LocalUserPassword.ps1" + ], + "Commands": [ + { + "Name": "Set-LocalUserPassword", + "Arguments": { + "UserName": "Administrator", + "Password": "$adminPassword", + "Force": true + } + } + ], + "RebootOnCompletion": 0 +} \ No newline at end of file diff --git a/functionaltests/api/v1/DummyTestApp/Resources/scripts/Get-DnsListeningIpAddress.ps1 b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Get-DnsListeningIpAddress.ps1 new file mode 100644 index 000000000..1db0b85f3 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Get-DnsListeningIpAddress.ps1 @@ -0,0 +1,7 @@ + +function Get-DnsListeningIpAddress { + Import-Module DnsServer + + (Get-DNSServer -ComputerName localhost).ServerSetting.ListeningIpAddress | + Where-Object { $_ -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" } +} diff --git a/functionaltests/api/v1/DummyTestApp/Resources/scripts/ImportCoreFunctions.ps1 b/functionaltests/api/v1/DummyTestApp/Resources/scripts/ImportCoreFunctions.ps1 new file mode 100644 index 000000000..85e64349a --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/scripts/ImportCoreFunctions.ps1 @@ -0,0 +1,68 @@ + +Import-Module CoreFunctions -Force +Initialize-Logger 'MuranoAgent' 'C:\Murano\PowerShell.log' + + +function Show-InvocationInfo { + param ( + $Invocation, + [Switch] $End + ) + + if ($End) { + Write-LogDebug "" + } + else { + Write-LogDebug "" + Write-LogDebug "" + foreach ($Parameter in $Invocation.MyCommand.Parameters) { + foreach ($Key in $Parameter.Keys) { + $Type = $Parameter[$Key].ParameterType.FullName + foreach ($Value in $Invocation.BoundParameters[$Key]) { + Write-LogDebug "[$Type] $Key = '$Value'" + } + } + } + Write-LogDebug "" + } +} + + +$TrapHandler = { + Write-LogError "" + Write-LogError $_ -EntireObject + Write-LogError "" + break +} + + +trap { + &$TrapHandler +} + +$ErrorActionPreference = 'Stop' + + +<# +# Usage example for Show-InvocationInfo + +function MyFunction { + param ( + [String] $Value1, + [String] $Value2, + [Int] $Int1 + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + # Main code here + } +} +#> diff --git a/functionaltests/api/v1/DummyTestApp/Resources/scripts/Install-RolePrimaryDomainController.ps1 b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Install-RolePrimaryDomainController.ps1 new file mode 100644 index 000000000..e8f1e5a9f --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Install-RolePrimaryDomainController.ps1 @@ -0,0 +1,43 @@ + +trap { + &$TrapHandler +} + + +Function Install-RolePrimaryDomainController { + param ( + [String] $DomainName, + [String] $SafeModePassword + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + Add-WindowsFeatureWrapper ` + -Name "DNS","AD-Domain-Services","RSAT-DFS-Mgmt-Con" ` + -IncludeManagementTools ` + -NotifyRestart + + Write-Log "Creating first domain controller ..." + + $SMAP = ConvertTo-SecureString -String $SafeModePassword -AsPlainText -Force + + $null = Install-ADDSForest ` + -DomainName $DomainName ` + -SafeModeAdministratorPassword $SMAP ` + -DomainMode Default ` + -ForestMode Default ` + -NoRebootOnCompletion ` + -Force + + Write-Log "Waiting 60 seconds for reboot ..." + Start-Sleep -Seconds 60 + } +} diff --git a/functionaltests/api/v1/DummyTestApp/Resources/scripts/Install-RoleSecondaryDomainController.ps1 b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Install-RoleSecondaryDomainController.ps1 new file mode 100644 index 000000000..be9258ed5 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Install-RoleSecondaryDomainController.ps1 @@ -0,0 +1,69 @@ + +trap { + &$TrapHandler +} + + +Function Install-RoleSecondaryDomainController +{ +<# +.SYNOPSIS +Install additional (secondary) domain controller. + +#> + param + ( + [String] + # Domain name to join to. + $DomainName, + + [String] + # Domain user who is allowed to join computer to domain. + $UserName, + + [String] + # User's password. + $Password, + + [String] + # Domain controller recovery mode password. + $SafeModePassword + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + $Credential = New-Credential -UserName "$DomainName\$UserName" -Password $Password + + # Add required windows features + Add-WindowsFeatureWrapper ` + -Name "DNS","AD-Domain-Services","RSAT-DFS-Mgmt-Con" ` + -IncludeManagementTools ` + -NotifyRestart + + + Write-Log "Adding secondary domain controller ..." + + $SMAP = ConvertTo-SecureString -String $SafeModePassword -AsPlainText -Force + + Install-ADDSDomainController ` + -DomainName $DomainName ` + -SafeModeAdministratorPassword $SMAP ` + -Credential $Credential ` + -NoRebootOnCompletion ` + -Force ` + -ErrorAction Stop | Out-Null + + Write-Log "Waiting for restart ..." + # Stop-Execution -ExitCode 3010 -ExitString "Computer must be restarted to finish domain controller promotion." + # Write-Log "Restarting computer ..." + # Restart-Computer -Force + } +} diff --git a/functionaltests/api/v1/DummyTestApp/Resources/scripts/Join-Domain.ps1 b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Join-Domain.ps1 new file mode 100644 index 000000000..403ef798d --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Join-Domain.ps1 @@ -0,0 +1,67 @@ + +trap { + &$TrapHandler +} + + +Function Join-Domain { +<# +.SYNOPSIS +Executes "Join domain" action. + +Requires 'CoreFunctions' module +#> + param ( + [String] $DomainName = '', + [String] $UserName = '', + [String] $Password = '', + [String] $OUPath = '', + [Switch] $AllowRestart + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ($UserName -eq '') { + $UserName = 'Administrator' + } + + $Credential = New-Credential -UserName "$DomainName\$UserName" -Password $Password + + + if (Test-ComputerName -DomainName $DomainName -ErrorAction 'SilentlyContinue') { + Write-LogWarning "Computer already joined to domain '$DomainName'" + } + else { + Write-Log "Joining computer to domain '$DomainName' ..." + + if ($OUPath -eq '') { + Add-Computer -DomainName $DomainName -Credential $Credential -Force + } + else { + Add-Computer -DomainName $DomainName -Credential $Credential -OUPath $OUPath -Force + } + + $null = Exec 'ipconfig' @('/registerdns') -RedirectStreams + + Write-Log "Waiting 30 seconds to restart ..." + Start-Sleep -Seconds 30 + <# + if ($AllowRestart) { + Write-Log "Restarting computer ..." + Restart-Computer -Force + } + else { + Write-Log "Please restart the computer now." + } + #> + } + } +} diff --git a/functionaltests/api/v1/DummyTestApp/Resources/scripts/Set-LocalUserPassword.ps1 b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Set-LocalUserPassword.ps1 new file mode 100644 index 000000000..8708a0f4a --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/Resources/scripts/Set-LocalUserPassword.ps1 @@ -0,0 +1,37 @@ + +trap { + &$TrapHandler +} + + +Function Set-LocalUserPassword { + param ( + [String] $UserName, + [String] $Password, + [Switch] $Force + ) + begin { + Show-InvocationInfo $MyInvocation + } + end { + Show-InvocationInfo $MyInvocation -End + } + process { + trap { + &$TrapHandler + } + + if ((Get-WmiObject Win32_UserAccount -Filter "LocalAccount = 'True' AND Name='$UserName'") -eq $null) { + throw "Unable to find local user account '$UserName'" + } + + if ($Force) { + Write-Log "Changing password for user '$UserName' to '*****'" # :) + $null = ([ADSI] "WinNT://./$UserName").SetPassword($Password) + } + else { + Write-LogWarning "You are trying to change password for user '$UserName'. To do this please run the command again with -Force parameter." + } + } +} + diff --git a/functionaltests/api/v1/DummyTestApp/UI/ui.yaml b/functionaltests/api/v1/DummyTestApp/UI/ui.yaml new file mode 100644 index 000000000..9fab3d413 --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/UI/ui.yaml @@ -0,0 +1,165 @@ +Version: 2 + +Templates: + primaryController: + ?: + type: io.murano.windows.Dummy.PrimaryController + host: + ?: + type: io.murano.services.windows.Host + adminPassword: $.serviceConfiguration.adminPassword + name: generateHostname($.serviceConfiguration.unitNamingPattern, 1) + flavor: $.instanceConfiguration.flavor + image: $.instanceConfiguration.osImage + + secondaryController: + ?: + type: io.murano.windows.Dummy.SecondaryController + host: + ?: + type: io.murano.services.windows.Host + adminPassword: $.serviceConfiguration.adminPassword + name: generateHostname($.serviceConfiguration.unitNamingPattern, $index + 1) + flavor: $.instanceConfiguration.flavor + image: $.instanceConfiguration.osImage + +Application: + ?: + type: io.murano.windows.Dummy.Dummy + name: $.serviceConfiguration.name + primaryController: $primaryController + secondaryControllers: repeat($secondaryController, $.serviceConfiguration.dcInstances - 1) + +Forms: + - serviceConfiguration: + fields: + - name: configuration + type: string + hidden: true + initial: standalone + - name: name + type: string + label: Domain Name + description: >- + Enter a desired name for a new domain. This name should fit to + DNS Domain Name requirements: it should contain + only A-Z, a-z, 0-9, (.) and (-) and should not end with a dash. + DNS server will be automatically set up on each of the Domain + Controller instances. Note: Only first 15 characters or characters + before first period is used as NetBIOS name. + minLength: 2 + maxLength: 255 + validators: + - expr: + regexpValidator: '^([0-9A-Za-z]|[0-9A-Za-z][0-9A-Za-z-]*[0-9A-Za-z])\.[0-9A-Za-z][0-9A-Za-z-]*[0-9A-Za-z]$' + message: >- + Only letters, numbers and dashes in the middle are + allowed. Period characters are allowed only when they + are used to delimit the components of domain style + names. Single-level domain is not + appropriate. Subdomains are not allowed. + - expr: + regexpValidator: '(^[^.]+$|^[^.]{1,15}\..*$)' + message: >- + NetBIOS name cannot be shorter than 1 symbol and + longer than 15 symbols. + - expr: + regexpValidator: '(^[^.]+$|^[^.]*\.[^.]{2,63}.*$)' + message: >- + DNS host name cannot be shorter than 2 symbols and + longer than 63 symbols. + helpText: >- + Just letters, numbers and dashes are allowed. + A dot can be used to create subdomains + - name: dcInstances + type: integer + label: Instance Count + description: >- + You can create several Active Directory instances by setting + instance number larger than one. One primary Domain Controller + and a few secondary DCs will be created. + minValue: 1 + maxValue: 100 + initial: 1 + helpText: Enter an integer value between 1 and 100 + - name: adminAccountName + type: string + label: Account Name + initial: Administrator + regexpValidator: '^[-\w]+$' + errorMessages: + invalid: 'Just letters, numbers, underscores and hyphens are allowed.' + - name: adminPassword + type: password + label: Administrator password + descriptionTitle: Passwords + description: >- + Windows requires strong password for service administration. + Your password should have at least one letter in each + register, a number and a special character. Password length should be + a minimum of 7 characters. + + Once you forget your password you won't be able to + operate the service until recovery password would be entered. So it's + better for Recovery and Administrator password to be different. + - name: recoveryPassword + type: password + label: Recovery password + - name: assignFloatingIP + required: false + type: floatingip + label: Assign Floating IP + description: >- + Select to true to assign floating IP automatically to Primary DC + initial: false + required: false + widgetMedia: + css: {all: ['muranodashboard/css/checkbox.css']} + - name: unitNamingPattern + type: string + label: Hostname template + description: >- + For your convenience all instance hostnames can be named + in the same way. Enter a name and use # character for incrementation. + For example, host# turns into host1, host2, etc. Please follow Windows + hostname restrictions. + required: false + regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$' + # FIXME: does not work for # turning into 2-digit numbers + maxLength: 15 + helpText: Optional field for a machine hostname template + # temporaryHack + widgetMedia: + js: ['muranodashboard/js/support_placeholder.js'] + css: {all: ['muranodashboard/css/support_placeholder.css']} + validators: + # if unitNamingPattern is given and dcInstances > 1, then '#' should occur in unitNamingPattern + - expr: $.serviceConfiguration.dcInstances < 2 or not $.serviceConfiguration.unitNamingPattern.bool() or '#' in $.serviceConfiguration.unitNamingPattern + message: Incrementation symbol "#" is required in the Hostname template + - instanceConfiguration: + fields: + - name: title + type: string + required: false + hidden: true + descriptionTitle: Instance Configuration + description: Specify some instance parameters on which service would be created. + - name: flavor + type: flavor + label: Instance flavor + description: >- + Select registered in Openstack flavor. Consider that service performance + depends on this parameter. + required: false + - name: osImage + type: image + imageType: windows + label: Instance image + description: >- + Select valid image for a service. Image should already be prepared and + registered in glance. + - name: availabilityZone + type: azone + label: Availability zone + description: Select availability zone where service would be installed. + required: false diff --git a/functionaltests/api/v1/DummyTestApp/logo2.png b/functionaltests/api/v1/DummyTestApp/logo2.png new file mode 100644 index 000000000..e1a24717e Binary files /dev/null and b/functionaltests/api/v1/DummyTestApp/logo2.png differ diff --git a/functionaltests/api/v1/DummyTestApp/manifest.yaml b/functionaltests/api/v1/DummyTestApp/manifest.yaml new file mode 100644 index 000000000..ae7c6801c --- /dev/null +++ b/functionaltests/api/v1/DummyTestApp/manifest.yaml @@ -0,0 +1,26 @@ +Format: 1.0 + +Type: Application + +FullName: io.murano.windows.Dummy + +Name: Dummy + +Description: | + Dummy + +Author: 'murano.io' + +Tags: [Dummy] + +Classes: + io.murano.windows.Host: Host.yaml + io.murano.windows.DomainHost: DomainHost.yaml + io.murano.windows.Dummy.Dummy: Dummy.yaml + io.murano.windows.Dummy.Controller: Controller.yaml + io.murano.windows.Dummy.PrimaryController: PrimaryController.yaml + io.murano.windows.Dummy.SecondaryController: SecondaryController.yaml + +# UI: ui.yaml # default to ui.yaml, will use default if skipped + +Logo: logo2.png # defaults to logo.png, will use default if skipped diff --git a/functionaltests/api/v1/test_repository.py b/functionaltests/api/v1/test_repository.py index 6e12ac303..2e0755135 100644 --- a/functionaltests/api/v1/test_repository.py +++ b/functionaltests/api/v1/test_repository.py @@ -12,19 +12,63 @@ # License for the specific language governing permissions and limitations # under the License. +import os +import zipfile + from tempest.test import attr +#from tempest import exceptions +#Need uncomment after fix https://bugs.launchpad.net/murano/+bug/1309413 +#it will be use in tearDown method + from functionaltests.api import base -class TestRepository(base.TestCase): +class TestCaseRepository(base.TestCase): @classmethod def setUpClass(cls): - super(TestRepository, cls).setUpClass() + super(TestCaseRepository, cls).setUpClass() - raise cls.skipException("Murano Repository tests are disabled") + cls.location = os.path.realpath( + os.path.join(os.getcwd(), os.path.dirname(__file__))) + + __folderpath__ = os.path.join(cls.location, "DummyTestApp") + __rootlen__ = len(__folderpath__) + 1 + + with zipfile.ZipFile(os.path.join(cls.location, + "DummyTestApp.zip"), "w") as zf: + for dirname, _, files in os.walk(__folderpath__): + for filename in files: + fn = os.path.join(dirname, filename) + zf.write(fn, fn[__rootlen__:]) + + def setUp(self): + super(TestCaseRepository, self).setUp() + + self.packages = [] + + def tearDown(self): + super(TestCaseRepository, self).tearDown() + + for package in self.packages: + try: + self.client.delete_package(package['id']) + except Exception: + #except exceptions.NotFound: Need to uncomment after fix the + #following bug https://bugs.launchpad.net/murano/+bug/1309413 + pass + + @classmethod + def tearDownClass(cls): + + super(TestCaseRepository, cls).tearDownClass() + + os.remove(os.path.join(cls.location, "DummyTestApp.zip")) + + +class TestRepositorySanity(TestCaseRepository): @attr(type='smoke') def test_get_list_packages(self): @@ -33,65 +77,176 @@ class TestRepository(base.TestCase): self.assertEqual(200, resp.status) self.assertTrue(isinstance(body['packages'], list)) - @attr(type='smoke') - def test_get_package(self): - _, body = self.client.get_list_packages() - package = body['packages'][0] - - resp, body = self.client.get_package(package['id']) - - self.assertEqual(200, resp.status) - self.assertEqual(package, body) - - @attr(type='smoke') - def test_update_package(self): - _, body = self.client.get_list_packages() - package = body['packages'][0] - - resp, body = self.client.update_package(package['id']) - - self.assertEqual(200, resp.status) - self.assertIn("i'm a test", body['tags']) - - @attr(type='smoke') - def test_delete_package(self): - _, body = self.client.get_list_packages() - package = body['packages'][0] - - resp, _ = self.client.delete_package(package['id']) - - self.assertEqual(200, resp.status) - - @attr(type='smoke') - def test_download_package(self): - _, body = self.client.get_list_packages() - package = body['packages'][0] - - resp, _ = self.client.download_package(package['id']) - - self.assertEqual(200, resp.status) - - @attr(type='smoke') - def test_get_ui_definitions(self): - _, body = self.client.get_list_packages() - package = body['packages'][0] - - resp, _ = self.client.get_ui_definition(package['id']) - - self.assertEqual(200, resp.status) - - @attr(type='smoke') - def test_get_logo(self): - _, body = self.client.get_list_packages() - package = body['packages'][0] - - resp, _ = self.client.get_logo(package['id']) - - self.assertEqual(200, resp.status) - @attr(type='smoke') def test_get_list_categories(self): resp, body = self.client.list_categories() self.assertEqual(200, resp.status) self.assertTrue(isinstance(body['categories'], list)) + + @attr(type='smoke') + def test_upload_and_delete_package(self): + packages_list = self.client.get_list_packages()[1] + for package in packages_list['packages']: + if 'Dummy' in package['fully_qualified_name']: + self.client.delete_package(package['id']) + categorie = self.client.list_categories()[1]['categories'][0] + packages_list = self.client.get_list_packages()[1]['packages'] + + resp = self.client.upload_package( + 'testpackage', + {"categories": [categorie], "tags": ["windows"]}) + self.packages.append(resp.json()) + + _packages_list = self.client.get_list_packages()[1]['packages'] + + self.assertEqual(200, resp.status_code) + self.assertEqual(len(packages_list) + 1, len(_packages_list)) + + resp = self.client.delete_package(resp.json()['id'])[0] + + _packages_list = self.client.get_list_packages()[1]['packages'] + + self.assertEqual(200, resp.status) + self.assertEqual(len(packages_list), len(_packages_list)) + + +class TestRepository(TestCaseRepository): + + @classmethod + def setUpClass(cls): + + super(TestRepository, cls).setUpClass() + cls.categorie = cls.client.list_categories()[1]['categories'][0] + + def setUp(self): + super(TestRepository, self).setUp() + + packages_list = self.client.get_list_packages()[1] + for package in packages_list['packages']: + if 'Dummy' in package['fully_qualified_name']: + self.client.delete_package(package['id']) + self.package = self.client.upload_package( + 'testpackage', + {"categories": [self.categorie], "tags": ["windows"]}).json() + self.packages.append(self.package) + + @attr(type='smoke') + def test_get_package(self): + resp, body = self.client.get_package(self.package['id']) + + self.assertEqual(200, resp.status) + self.assertEqual(self.package['tags'], body['tags']) + + @attr(type='smoke') + def test_update_package(self): + post_body = [ + { + "op": "add", + "path": "/tags", + "value": ["im a test"] + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertIn("im a test", body['tags']) + + post_body = [ + { + "op": "replace", + "path": "/tags", + "value": ["im bad:D"] + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertNotIn("im a test", body['tags']) + self.assertIn("im bad:D", body['tags']) + + post_body = [ + { + "op": "remove", + "path": "/tags", + "value": ["im bad:D"] + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertNotIn("im bad:D", body['tags']) + + post_body = [ + { + "op": "replace", + "path": "/is_public", + "value": True + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertTrue(body['is_public']) + + post_body = [ + { + "op": "replace", + "path": "/enabled", + "value": True + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertTrue(body['enabled']) + + post_body = [ + { + "op": "replace", + "path": "/description", + "value": "New description" + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertEqual("New description", body['description']) + + post_body = [ + { + "op": "replace", + "path": "/name", + "value": "New name" + } + ] + + resp, body = self.client.update_package(self.package['id'], post_body) + + self.assertEqual(200, resp.status) + self.assertEqual("New name", body['name']) + + @attr(type='smoke') + def test_download_package(self): + resp = self.client.download_package(self.package['id'])[0] + + self.assertEqual(200, resp.status) + + @attr(type='smoke') + def test_get_ui_definitions(self): + resp = self.client.get_ui_definition(self.package['id'])[0] + + self.assertEqual(200, resp.status) + + @attr(type='smoke') + def test_get_logo(self): + resp, body = self.client.get_logo(self.package['id']) + + self.assertEqual(200, resp.status) + self.assertTrue(isinstance(body, str)) diff --git a/functionaltests/run_tests.sh b/functionaltests/run_tests.sh index a8dc82f5f..b8d4af7c5 100755 --- a/functionaltests/run_tests.sh +++ b/functionaltests/run_tests.sh @@ -28,4 +28,4 @@ TEMPEST_DIR=${TEMPEST_DIR:-/opt/stack/new/tempest} # Add tempest source tree to PYTHONPATH export PYTHONPATH=$PYTHONPATH:$TEMPEST_DIR -nosetests -v . +nosetests -sv .